• UVA1386 【Cellular Automaton】题解


    题面:UVA1386 Cellular Automaton

    矩阵乘法+快速幂解法:

    这是一个比较裸的有点复杂需要优化的矩乘快速幂,所以推荐大家先做一下下列洛谷题目练练手:

    (会了,差不多就是多倍经验题了

    注:如果你还不会矩阵乘法,可以移步了解一下P3390的题解

    P1939 【模板】矩阵加速(数列)

    P3390 【模板】矩阵快速幂

    P1962 斐波那契数列

    P4910 帕秋莉的手环

    P4838 P哥破解密码

    然后讲一下本题,读题我们发现这个环上所进行的 k 次操作都是一模一样的,还是相邻的数的和,于是想到用矩阵快速幂来写。

    解题思路:

    我们可以根据 d 的值来建出基础矩阵,举个例子(样例):

    					1 1 0 0 1
    					1 1 1 0 0
    					0 1 1 1 0
    					0 0 1 1 1
    					1 0 0 1 1
    

    这是样例每一次操作所对应的乘法矩阵。我们要求出 k 次操作后的环上的元素等同于乘以 k 次这个矩阵,而这个过程可以直接用快速幂优化成log复杂度。

    于是我们便可以光明正大的提交,然后光荣 TLE 了

    为什么呢?我们看到数据范围:$ n leq 500 $ !众所周知:矩阵乘法中矩阵上每一个元素都会被用行乘列的方式更新,所以复杂度为 (n^3),在乘上快速幂的一个log n ,不TLE才怪!

    优化方案:

    于是我们尝试优化一下(把那个 $ n^3 $ 降到 $ n^2 $ ):我们再看到上面样例的矩阵,可以发现它的下一行等于上一行所有的 1 向右移一位。所以我们进行矩阵乘法时可以不更新矩阵的每一个位置,而是只更新它的第一行(将它压缩一下)其它几行可以通过第一行推出来。

    例如:

    					0 2 1 1 2 
    

    我们可以通过这一行推出整个矩阵(向右移一位):

    					0 2 1 1 2 
    					2 0 2 1 1 
    					1 2 0 2 1 
    					1 1 2 0 2 
    					2 1 1 2 0
    

    于是乎,我们写一个新的”矩阵乘法“代码如下:

    void cheng(ll a[],ll b[]){
        for(int i=1;i<=n;i++)//预处理 1
            res[i]=0,s[1][i]=b[i];
        for(int i=2;i<=n;i++){
            s[i][1]=s[i-1][n];
            for(int j=2;j<=n;j++)
                s[i][j]=s[i-1][j-1];
        }// 解压一维矩阵,复杂度不会变回 n^3!
        for(int i=1;i<=n;i++)
            for(int k=1;k<=n;k++)
                res[i]+=(a[k]*s[i][k])%m,res[i]%=m;
        for(int i=1;i<=n;i++)a[i]=res[i];
    }// 本题专用“矩阵乘法”
    //请不要直接给a数组赋值,不然后果自负!
    

    然后在用快速幂优化一下:

    while(k){
    			if(k&1)cheng(ans,base);
    			cheng(base,base);
    			k>>=1;
    		}
    

    完整代码:

    #include<iostream>
    #include<cstdio>
    #include<iomanip>
    #include<algorithm>
    
    #define ll long long
    #define db double
    #define inf 0x7fffffff
    
    using namespace std;
    
    const int l=501;
    int n,m,d,k;
    ll s[l][l];
    ll ans[l],base[l],res[l];
    // ans=(answer),base(基础矩阵),res=(result)
    // s数组是用来解压矩阵的(将一维矩阵换成二维)
    
    inline ll qr(){
        char ch;
        while((ch=getchar())<'0'||ch>'9');
        ll res=ch^48;
        while((ch=getchar())>='0'&&ch<='9')
            res=(res<<1)+(res<<3)+(ch^48);
        return res;
    }
    
    void cheng(ll a[],ll b[]){
        for(int i=1;i<=n;i++)//预处理 1
            res[i]=0,s[1][i]=b[i];
        for(int i=2;i<=n;i++){
            s[i][1]=s[i-1][n];
            for(int j=2;j<=n;j++)
                s[i][j]=s[i-1][j-1];
        }// 解压一维矩阵,复杂度不会变回 n^3!
        for(int i=1;i<=n;i++)
            for(int k=1;k<=n;k++)
                res[i]+=(a[k]*s[i][k])%m,res[i]%=m;
        for(int i=1;i<=n;i++)a[i]=res[i];
    }// 本题专用“矩阵乘法”
    //请不要直接给a数组赋值,不然后果自负!
    
    int main(){
        while(scanf("%d%d%d%d",&n,&m,&d,&k)!=EOF){
            for(int i=1;i<=n;i++)//预处理 2
                ans[i]=qr(),base[i]=0;
            for(int i=0;i<=d;i++)//单位矩阵
                base[1+i]=base[(n-i)%n+1]=1;
            while(k){
                if(k&1)cheng(ans,base);
                cheng(base,base);
                k>>=1;
            }// 只有四行的快速幂
            for(int i=1;i<n;++i){
                printf("%lld ",ans[i]);
            }printf("%lld
    ",ans[n]);
        }
        return 0;
    }
    
    //预处理 1:将res数组归零,将b数组赋值给s数组第一维以便压缩
    
    //预处理 2:给ans数组赋初值,将单位矩阵清零
    

    注:解压矩阵会消耗很多时间,也可以不解压,但乘起来会有些麻烦(蒟蒻手残,就不压行了吧)。

    ✐☎博主撰文不易,转载还请注明出处;若对本文有疑,请私信或在下方讨论中提出。O(∩_∩)O谢谢!☏

    ☃〔尽管小伙伴们肯定有千百种方式针对,但博主还是极其非常十分不要脸的把反对键吃掉辣!〕☃

    ✿『$At$ $last$:非常一(hu)本(shuo)正(ba)经(dao)的:博主很笨,请不要欺负他』✿✍

  • 相关阅读:
    基础数据类型补充
    编码转换
    is 和 == 的区别
    字典 dict
    列表与元组
    python基础第一节
    poll函数
    基本 TCP 的回射服务器
    文件IO
    base | AtomicIntegerT类
  • 原文地址:https://www.cnblogs.com/812-xiao-wen/p/9871484.html
Copyright © 2020-2023  润新知