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)的:博主很笨,请不要欺负他』✿✍

原文地址:https://www.cnblogs.com/812-xiao-wen/p/9871484.html