zoukankan      html  css  js  c++  java
  • 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);
    }
  • 相关阅读:
    安全编码1
    VPP tips
    VPP概述汇总
    C语言安全编码摘录
    TCP-proxy
    Scipy Lecture Notes学习笔记(一)Getting started with Python for science 1.4. Matplotlib: plotting
    Scipy Lecture Notes学习笔记(一)Getting started with Python for science 1.3. NumPy: creating and manipulating numerical data
    Scipy Lecture Notes学习笔记(一)Getting started with Python for science 1.2. The Python language
    Scipy Lecture Notes学习笔记(一)Getting started with Python for science 1.1. Python scientific computing ecosystem
    25马5跑道,求最快的五匹马的需要比赛的次数
  • 原文地址:https://www.cnblogs.com/dilthey/p/9953042.html
Copyright © 2011-2022 走看看