P5789 可乐

one_cell

2020-03-10 15:50:00

Solution

## 矩阵快速幂 ### 原创思路还是要感谢Zhang_RQ神仙 #### 这道题是让我们求在一个n个点,m条边的无向图中从1号点出发,走T步,每一步可以选一种操作,求方案个数。 #### 假设目前在u点,操作有以下3种: #### ①若存在一条u-v的边,可以从u走到v #### ②原地不动 #### ③原地自爆 #### //具体题意可以看https://www.luogu.com.cn/problem/P3758 ------------ ### 首先思考如何建图:由于n很小,可以直接邻接矩阵。但原地不动和自爆的情况如何考虑? #### ①原地不动:等价于往自己连了一个自环,令f[i][i]=1 #### ②原地自爆:我们可以建一个表示自爆的0号城市。则不难发现,只要从1~n这些点都向0号点连一条单向边即可,令f[i][0]=1 ------------ ### 暴力搜索一定没有前途,T<=1e9 ### 一. 我们思考如何在图中进行DP: ### 设f[i][j]表示从i走到j的方案个数,不难发现:f[i][j]=∑f[i][k]*f[k][j](乘法原理) ### 则Answer=∑ f[1][i] ------------ ### 那我们跑一遍DP,即可求出答案(有点像Floyd) ### 注意:这里的DP****并不是O(n^3)****,我们实际的状态是f[t][i][j],有一个走了t步的限制 ### 则时间复杂度为O(T*n^2) ### T<=1e9,还是不行 ------------ ### 思考如何优化 ### 二. 考虑矩阵乘法 ### 我们发现:这里的n,m<=100,但T<=1e9。两个数大小相差甚远 ### 一般这种情况可以考虑如何用矩乘优化 ------------ ### 我们观察矩阵乘法的基本形式 matrix operator * (const matrix &A,const matrix &B) { matrix C; C.n=A.n;C.m=B.m; for(int i=0;i<=C.n;i++) for(int j=0;j<=C.m;j++) for(int k=0;k<=A.m;k++) { C.c[i][j]=(C.c[i][j]+A.c[i][k]*B.c[k][j])%mod; } return C; } ### 定理:设现在有一个矩阵A,则A^k含义即是,对于一个A[i][j],表示从i到j走k步的方案总数 ------------ ## 证明: ### 先抛开矩阵乘法,单纯地思考用DP如何做这道题。 #### 设f[i][j][k]表示从i到j,走了k步的方案个数。 #### 初始化:f[i][j][0]没有意义。我们直接看k=1时的初始化 #### 当k=1时其实就是这张图的邻接矩阵。对于一条边u-v,我们令f[u][v][1]=f[v][u][1]=1 #### 然后考虑状态转移: #### f[i][j][k]=∑ f[i][p][k-1]*f[p][j][1] #### 然后我们发现:k这一维只与k-1维有关系。所以我们可以省略这一维。就变成了f[i][j]=∑ f[i][p]*f[p][j] #### 然后与朴素的快速幂一比较,发现大同小异,这样问题其实就转化了求A矩阵的T次方 #### 则以上问题就得到了证明 ------------ ### 一遍矩阵快速幂,轻松跑过~ ## 详见代码: #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int N=110,mod=2017; int n,m,T; struct matrix { int n,m,c[N][N]; matrix() { n=m=0; memset(c,0,sizeof(c)); }//初始化 }; matrix operator * (const matrix &A,const matrix &B) {//自定义函数(乘法) matrix C; C.n=A.n;C.m=B.m; for(int i=0;i<=C.n;i++) for(int j=0;j<=C.m;j++) for(int k=0;k<=A.m;k++) { C.c[i][j]=(C.c[i][j]+A.c[i][k]*B.c[k][j])%mod; } return C; }//普通的矩阵乘法 matrix ksm(matrix a,int b) { matrix ans; ans.n=ans.m=n; for(int i=0;i<=n;i++)ans.c[i][i]=1; //对角矩阵,累计答案 while(b)//与正常的快速幂同理 { if(b&1)ans=ans*a; a=a*a; b>>=1; } return ans; }//矩阵快速幂 int main() { scanf("%d%d",&n,&m); matrix t; t.n=n;t.m=n; for(int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); t.c[u][v]=t.c[v][u]=1; //原图中的邻接矩阵 } for(int i=0;i<=n;i++)t.c[i][i]=1; for(int i=1;i<=n;i++)t.c[i][0]=1; //连自环和0号城市 scanf("%d",&T); matrix res=ksm(t,T);//T次方即是答案 int ans=0; for(int i=0;i<=n;i++)ans=(ans+res.c[1][i])%mod; //统计答案 printf("%d",ans); return 0; }