UVA1386 【Cellular Automaton】题解
矩阵乘法+快速幂解法:
这是一个比较裸的有点复杂需要优化的矩乘快速幂,所以推荐大家先做一下下列洛谷题目练练手:
(会了,差不多就是多倍经验题了)
注:如果你还不会矩阵乘法,可以移步了解一下P3390的题解
然后讲一下本题,读题我们发现这个环上所进行的 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\n",ans[n]);
}
return 0;
}
//预处理 1:将res数组归零,将b数组赋值给s数组第一维以便压缩
//预处理 2:给ans数组赋初值,将单位矩阵清零
注:解压矩阵会消耗很多时间,也可以不解压,但乘起来会有些麻烦(蒟蒻手残,就不压行了吧)。