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);

}

 

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