• CH 3401


    题目链接:传送门

    描述
    石头游戏在一个 $n$ 行 $m$ 列 ($1 le n,m le 8$) 的网格上进行,每个格子对应一种操作序列,操作序列至多有 $10$ 种,分别用 $0 sim 9$ 这 $10$ 个数字指明。
    操作序列是一个长度不超过 $6$ 且循环执行、每秒执行一个字符的字符串。每秒钟,所有格子同时执行各自操作序列里的下一个字符。序列中的每个字符是以下格式之一:
    数字 $0 sim 9$:表示拿 $0 sim 9$ 个石头到该格子。
    $NWSE$:表示把这个格子内所有的石头推到相邻的格子,$N$ 表示上方,$W$ 表示左方,$S$ 表示下方,$E$ 表示右方。
    $D$:表示拿走这个格子的所有石头。
    给定每种操作序列对应的字符串,以及网格中每个格子对应的操作序列,求石头游戏进行了 t 秒之后,石头最多的格子里有多少个石头。在游戏开始时,网格是空的。

    输入格式
    第一行4个整数 $n, m, t, act$。
    接下来 $n$ 行,每行 $m$ 个字符,表示每个格子对应的操作序列。
    最后 $act$ 行,每行一个字符串,表示从 $0$ 开始的每个操作序列。

    输出格式
    一个整数:游戏进行了 $t$ 秒之后,所有方格中最多的格子有多少个石头。

    样例输入
    1 6 10 3
    011112
    1E
    E
    0

    样例输出
    3

    样例解释
    这是另一个类似于传送带的结构。左边的设备0间隔地产生石头并向东传送。设备1向右传送,直到设备2。10秒后,总共产生了5个石头,2个在传送带上,3个在最右边。

    题解:

    (以下参考李煜东《算法竞赛进阶指南》)

    一般来说,若一类问题具有以下特点:

    1. 可以抽象出一个长度为 $n$ 的一维向量(称作状态矩阵),该向量在每个单位时间发生一次变化。
    2. 变化的形式是一个线性递推:将状态矩阵乘上一个转移矩阵,即可得下一秒时的状态矩阵。
    3. 该递推式在每个时间可能作用于不同的数据,但本身始终不变。
    4. 向量变化时间很长,即递推的轮次很多,但是向量本身长度不长。

    那么,可以考虑使用矩阵快速幂加速递推。

    回到本题:

    由于状态矩阵需要是一维向量的缘故,不妨把网格重组为一维向量,这很简单,原来网格的第 $i$ 行第 $j$ 列变为现在的第 $pos(i,j) = (i-1)*m + j$ 列。

    即设 $F_{k}[pos(i,j)]$ 代表第 $k$ 秒时,网格第 $i$ 行第 $j$ 列中的石头数目。

    考虑到本题中存在一个可以无限取石头的“石头源”,因此不妨对状态矩阵再添加第 $0$ 列,对任意时刻 $k$ 始终有 $F_{k}[0] = 1$,作为“石头源”。

    初始化状态矩阵 $F_{k} = [1, 0, 0, cdots, 0]$。

    注意到操作序列长度不超过 $6$,$1 sim 6$ 的最小公倍数为 $60$,也就是说,不妨把全部操作序列都补成长度为 $60$ 的操作序列,这样一来所有操作的循环节固定为 $60$。

    不妨统计出第 $k(1 le k le 60)$ 秒各个格子都执行什么操作,假设全部操作可由方阵 $A_k$ 表示,则求 $A_k$ 的方法如下:

    1. 若某个网格 $(i,j)$ 在第 $k$ 秒执行 $N$ 操作,那么不难知道 $A_k[pos(i,j),pos(i-1,j)]$ 是决定了网格 $(i,j)$ 对下一秒网格 $(i-1,j)$ 贡献的系数,因此令 $A_k[pos(i,j),pos(i-1,j)] = 1$。其他平推操作同理。
    2. 若某个网格 $(i,j)$ 在第 $k$ 秒执行取 $x$ 个石头的操作,首先,原本格子上有的石头要保留,因此 $A_k[pos(i,j),pos(i,j)] = 1$(这个好像书上没有讲到);其次,从“石头源” $F_{k}[0]$ 拿取石头,乘上 $A_k[0,pos(i,j)] = x$ 倍,放入网格 $(i,j)$ 即可。
    3. 始终令 $A_k[0,0] = 1$,即“石头源”始终保持不变。
    4. 若两个网格 $(i_1,j_1)$ 和 $(i_2,j_2)$ 之间没有联系,则 $A_k[pos(i_1,j_1),pos(i_2,j_2)] = 0$。若网格 $(i,j)$ 扔掉本格全部石头,则 $A_k[pos(i,j),pos(i,j)] = 0$。

    使用矩阵快速幂加速递推时,遇到常数项,可以在状态矩阵中增加一列常数列,始终存储 $1$,它乘上适当的系数后,可以为状态矩阵的其他任何列提供任何常数。

    在算出了 $A_1 sim A_{60}$ 之后,我们就可以分割 $t = q imes 60 + r(0 le r < 60)$,就可以有 $F_t = F_0 imes (A_1 imes cdots imes A_{60})^q imes (A_1 imes cdots imes A_r)$。

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;
    typedef long long ll;
    
    const int DIM=70;
    int dim;
    struct Matrix
    {
        ll mat[DIM][DIM];
        Matrix operator*(Matrix const &oth)const
        {
            Matrix res;
            memset(res.mat,0,sizeof(res.mat));
            for(int i=0;i<dim;i++)
                for(int j=0;j<dim;j++)
                    for(int k=0;k<dim;k++)
                        res.mat[i][j]+=mat[i][k]*oth.mat[k][j];
            return res;
        }
    }A,a[65],F0,Ft;
    Matrix fpow(Matrix base,ll n)
    {
        Matrix res;
        memset(res.mat,0,sizeof(res.mat));
        for(int i=0;i<dim;i++) res.mat[i][i]=1;
        while(n)
        {
            if(n&1) res=res*base;
            base=base*base;
            n>>=1;
        }
        return res;
    }
    
    int n,m,t,q;
    int type[15][15];
    char op[15][65];
    inline int pos(int i,int j){return (i-1)*m+j;}
    int main()
    {
        scanf("%d%d%d%d",&n,&m,&t,&q);
        dim=n*m+1;
        for(int i=1;i<=n;i++)
        {
            char tmp[15];
            scanf("%s",tmp+1);
            for(int j=1;j<=m;j++) type[i][j]=tmp[j]-'0';
        }
    
        for(int i=0;i<q;i++)
        {
            scanf("%s",op[i]);
            int len=strlen(op[i]);
            for(int k=len;k<60;k++) op[i][k]=op[i][k%len];
            op[i][60]='';
        }
    
        memset(A.mat,0,sizeof(A.mat));
        for(int i=0;i<=n*m;i++) A.mat[i][i]=1;
    
        for(int k=1;k<=60;k++)
        {
            memset(a[k].mat,0,sizeof(a[k].mat));
    
            a[k].mat[0][0]=1;
            for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                int t=type[i][j];
                char o=op[t][k-1];
    
                if('0'<=o && o<='9') {
                    a[k].mat[pos(i,j)][pos(i,j)]=1;
                    a[k].mat[0][pos(i,j)]=o-'0';
                    continue;
                }
                if(o=='N' && i>1) {
                    a[k].mat[pos(i,j)][pos(i-1,j)]=1;
                    continue;
                }
                if(o=='S' && i<n) {
                    a[k].mat[pos(i,j)][pos(i+1,j)]=1;
                    continue;
                }
                if(o=='W' && j>1) {
                    a[k].mat[pos(i,j)][pos(i,j-1)]=1;
                    continue;
                }
                if(o=='E' && j<m) {
                    a[k].mat[pos(i,j)][pos(i,j+1)]=1;
                    continue;
                }
            }
            A=A*a[k];
        }
    
        memset(F0.mat,0,sizeof(F0.mat));
        F0.mat[0][0]=1;
    
        if(t/60>0) Ft=F0*fpow(A,t/60);
        else Ft=F0;
        for(int i=1;i<=t%60;i++) Ft=Ft*a[i];
    
        ll mx=0;
        for(int j=1;j<=n*m;j++) mx=max(mx,Ft.mat[0][j]);
        printf("%lld
    ",mx);
    }
  • 相关阅读:
    net.sf.json Maven依赖配置
    springboot 测试 出错
    PowerDesigner 中SQL文件、数据库表反向生成PDM
    魔板问题(搜索)
    九宫重排(搜索)
    选点(树的遍历)
    【搜索】桐桐的运输方案
    细胞(搜索)
    传球游戏(dp)
    脚本_检测mysql存活状态
  • 原文地址:https://www.cnblogs.com/dilthey/p/9953042.html
Copyright © 2020-2023  润新知