AcWing206 石子游戲 矩陣乘法

矩陣乘法經典例題。

 

做一個映射  $cal(i,j)=(i-1)*n+j$

把網格看成長度爲$n*m$的一維向量,定義一個 $1$ 行 $n*m+1$ 列的狀態矩陣 $F$,將其轉換成一維數組 。

F_{cal(i,j)} 表示網格的 (i,j)$(i,j)$ 位置石子個數, F_0 初始化1且始終爲1,象徵着給別的網格分發石子的“上帝”。

操作長度不超過6,1~6的最小公倍數爲60,所以每經過60秒,每個網格的操作序列一定處於最開始的字符處。

因此可以把1~60的操作矩陣相乘,再快速冪算它的 $t/60$ 次方,再乘以剩下的1到 $t\mod60$ 秒的操作矩陣,再被初始的狀態矩陣 $F$ 乘即可。注意順序不能錯!!!!

構造操作矩陣很簡單,設 A_k 爲第 k 秒的操作矩陣。將其定義成$n*m+1$$n*m+1$列的矩陣,及一個二維數組。

A_k[x][y] 表示映射爲x的網格在第k秒對映射爲y的網格的影響。

 

說幾個坑點:

1、注意WESN這些挪石子會挪出邊界,此時應該直接拋棄這些石子,否則在轉移矩陣上會錯誤的移往別的位置。

2、 注意開long long,會爆int

3、long long用%lld輸出

4、用來參加乘法的矩陣初始化應該是單位陣,而不是零陣

5、數字之間沒有空格時不能用scanf("%d")!!! sacnf("%c") 會讀取換行符,記得getchar()!!

6、矩陣乘法沒有交換律!!!!!!!!

整個過程的矩陣乘法是 F*\underbrace{A_1*A_2*A_3*\cdots*A_{60}}_{t/60}*A_1*A_2*A_3*\cdots*A_{t\mod60}

可以先做一個60的循環算出Sum=A_1*A_2*A_3*\cdots*A_{60}

在這個過程中可以順便把Extra=A_1*A_2*A_3*\cdots*A_{t\mod60} 算出來

如果 t<60 那我們不用算出Sum,把Extra 算出來後,此時的 Sum 並不是完整的A_1*A_2*A_3*\cdots*A_{60},但是Sum^{t/60}=Sum^0=E(單位陣),F*Sum^{t/60}*Extra 仍是正確答案;

當 t>60 時,一定要注意是F*Sum^{t/60}*Extra 而不是 F*Extra*Sum^{t/60},調了半天,吐血。。。

#include <cstdio>
#include <string>
#include <iostream>
#include <cstring>
#include <algorithm>
#define LL long long
#define mc(a,b) memcpy(a,b,sizeof(b))
#define ms(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N=71;
LL A[N][N],f[N],sum[N][N],extra[N][N];
int n,m,t,act,ac[11][11];
string s[15];
int cal(int x,int y)
{return (x-1)*n+y;}
void mul1(LL a[N][N],LL b[N][N])
{
    LL c[N][N];
    ms(c,0);
    for(int i=0;i<=n*m;i++)
        for(int j=0;j<=n*m;j++)
            for(int k=0;k<=n*m;k++) c[i][j]+=a[i][k]*b[k][j];
    mc(a,c);
}
void mul2(LL a[N],LL b[N][N])
{
    LL c[N];
    ms(c,0);
    for(int j=0;j<=n*m;j++)
        for(int k=0;k<=n*m;k++) c[j]+=a[k]*b[k][j];
    mc(a,c);
}
void qpow(LL a[N][N],int b)
{
    LL c[N][N];
    ms(c,0);//中間矩陣初始化
    for(int i=0;i<=n*m;i++)
        c[i][i]=1;
    while(b)
    {
        if(b&1)
            mul1(c,a);
        b>>=1;
        mul1(a,a);
    }
    mc(a,c);//中間矩陣還原給傳遞過來的矩陣
}
int main()
{
    f[0]=1;
    scanf("%d%d%d%d",&n,&m,&t,&act);
    getchar();//scanf不讀最後的空格
    for(int i=1;i<=n;i++)//連在一起的數字不能直接用scanf integer,讀入char=,再轉換
    {
        for(int j=1;j<=m;j++)
        {
            char c;
            scanf("%c",&c);//有scanf char 注意是否有換行
            ac[i][j]=c-'0';
        }
        getchar();
    }
    for(int i=0;i<act;i++)
        cin>>s[i];
    for(int i=0;i<=n*m;i++)
        extra[i][i]=sum[i][i]=1;
    for(int it=0;it<60&&it<t;it++)
    {
        ms(A,0);
        A[0][0]=1;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                string tmp=s[ac[i][j]];
                char tmpp=tmp[it%tmp.size()];
                int pos=cal(i,j);
                if(tmpp=='D') continue;
                if(tmpp>='0'&&tmpp<='9')
                {
                    A[0][pos]=tmpp-'0';
                    A[pos][pos]=1;
                }
                else
                {
                    if(tmpp=='E'&&j<m)
                        A[pos][cal(i,j+1)]=1;
                    if(tmpp=='W'&&j>1)
                        A[pos][cal(i,j-1)]=1;
                    if(tmpp=='N'&&i>1)
                        A[pos][cal(i-1,j)]=1;
                    if(tmpp=='S'&&i<n)
                        A[pos][cal(i+1,j)]=1;
                }
            }
        mul1(sum,A);
        if((it+1)==(t%60))
            mc(extra,sum);
    }
    int cnt=t/60;
    qpow(sum,cnt);
    mul2(f,sum);
    mul2(f,extra);
    LL maxn=0;  
    for(int i=1;i<=n*m;i++)
        maxn=max(maxn,f[i]);
    printf("%lld",maxn);

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章