[关闭]
@xiaoziyao 2021-05-08T14:48:34.000000Z 字数 14407 阅读 1749

矩阵加速图上问题题型总结

题型总结 学习笔记


分类:题型总结矩阵加速图上问题,简称

更好的阅读体验

前置知识

引入

我们看一道题:P2233 [HNOI2002]公交车路线

题意:有个车站~围成一圈,相邻的车站可以互相到达(比如),从车站出发换次车到达车站有多少种方案,注意,到达车站后将不会继续行动。

数据范围:

分析:

这道题是一道图上dp裸题,我们可以定义为到达车站的方案数,并枚举换车次数来更新它:每一次更新的时候枚举出发车站与到达车站,只要这两个车站可以相互到达,且出发车站不是,那么我们就对到达车站的进行更新。

核心代码:

  1. inline int abs(int x){
  2. return x<0? -x:x;
  3. }
  4. inline int check(int a,int b){//判断能否到达
  5. return (a==1&&b==8)||(a==8&&b==1)||abs(a-b)==1;
  6. }
  7. tot[1]=1;//从车站A出发
  8. for(t=1;t<=n;t++){
  9. for(i=1;i<=8;i++)
  10. tmp[i]=0;
  11. for(i=1;i<=8;i++)//枚举出发车站
  12. for(j=1;j<=8;j++)//枚举到达车站
  13. if(i!=5&&check(i,j))
  14. tmp[j]=(tmp[j]+tot[i])%mod;
  15. for(i=1;i<=8;i++)
  16. tot[i]=tmp[i];
  17. }

但是这份代码交上去后会T,获得的好成绩。

啊这……

怎么办呢?

我们可以尝试给这份代码换一个形式,再看看有什么特殊的地方:

  1. a[1][2]=a[1][8]=1;//a为可以相互到达点组成的邻接矩阵
  2. a[2][3]=a[2][1]=1;
  3. a[3][2]=a[3][4]=1;
  4. a[4][3]=a[4][5]=1;
  5. //这里没有a[5][4]=a[5][6]=1的原因是到了车站E就不能继续行动了
  6. a[6][5]=a[6][7]=1;
  7. a[7][6]=a[7][8]=1;
  8. a[8][7]=a[8][1]=1;
  9. tot[1]=1;
  10. for(t=1;t<=n;t++){
  11. for(i=1;i<=8;i++)
  12. tmp[i]=0;
  13. for(i=1;i<=8;i++)
  14. for(j=1;j<=8;j++)
  15. if(a[j][i])
  16. tmp[i]=(tmp[i]+tot[j])%mod;
  17. for(i=1;i<=8;i++)
  18. tot[i]=tmp[i];
  19. }

诶诶诶诶诶,好像发现了什么不得了的东西。

让我们再换个形式,让它更加明了:

  1. a[1][2]=a[1][8]=1;
  2. a[2][3]=a[2][1]=1;
  3. a[3][2]=a[3][4]=1;
  4. a[4][3]=a[4][5]=1;
  5. a[6][5]=a[6][7]=1;
  6. a[7][6]=a[7][8]=1;
  7. a[8][7]=a[8][1]=1;
  8. //注意,这里tmp和tot多加了一维
  9. tot[1][1]=1;
  10. for(t=1;t<=n;t++){
  11. for(i=1;i<=1;i++)//这个i的值始终为1,不用管
  12. for(j=1;j<=8;j++)
  13. tmp[i][j]=0;
  14. for(i=1;i<=1;i++)//这个i的值始终为1,不用管
  15. for(j=1;j<=8;j++)
  16. for(k=1;k<=8;k++)
  17. tmp[i][j]=(tmp[i][j]+tot[i][k]*a[k][j]%mod)%mod;//其实就相当于a[k][j]等于1就更新tot
  18. for(i=1;i<=8;i++)
  19. tot[1][i]=tmp[1][i];
  20. }

我们对比一下矩阵乘法的代码:

  1. matrix mul(matrix a,matrix b){
  2. matrix res;
  3. res.len=a.len,res.wid=b.wid;
  4. for(int i=1;i<=res.len;i++)
  5. for(int j=1;j<=res.wid;j++)
  6. res.mt[i][j]=0;
  7. for(int i=1;i<=a.len;i++)
  8. for(int j=1;j<=b.wid;j++)
  9. for(int k=1;k<=a.wid;k++)
  10. res.mt[i][j]=(res.mt[i][j]+a.mt[i][k]*b.mt[k][j]%mod)%mod;
  11. return res;
  12. }

!!!

加上比较大的数据范围,你想到了什么?

!矩阵快速幂!

我们直接重构代码,上矩阵快速幂:

  1. #include<stdio.h>
  2. const int maxk=10,mod=1000;
  3. int i,j,k,m,n;
  4. struct matrix{
  5. int len,wid;
  6. int mt[maxk][maxk];
  7. };
  8. matrix mul(matrix a,matrix b){
  9. matrix res;
  10. res.len=a.len,res.wid=b.wid;
  11. for(int i=1;i<=res.len;i++)
  12. for(int j=1;j<=res.wid;j++)
  13. res.mt[i][j]=0;
  14. for(int i=1;i<=a.len;i++)
  15. for(int j=1;j<=b.wid;j++)
  16. for(int k=1;k<=a.wid;k++)
  17. res.mt[i][j]=(res.mt[i][j]+a.mt[i][k]*b.mt[k][j]%mod)%mod;
  18. return res;
  19. }
  20. int calc(int b){
  21. matrix a,res;
  22. a.len=a.wid=8;
  23. for(int i=1;i<=a.len;i++)
  24. for(int j=1;j<=a.wid;j++)
  25. a.mt[i][j]=0;
  26. a.mt[1][2]=a.mt[1][8]=1;
  27. a.mt[2][3]=a.mt[2][1]=1;
  28. a.mt[3][2]=a.mt[3][4]=1;
  29. a.mt[4][3]=a.mt[4][5]=1;
  30. a.mt[6][5]=a.mt[6][7]=1;
  31. a.mt[7][6]=a.mt[7][8]=1;
  32. a.mt[8][7]=a.mt[8][1]=1;
  33. res.len=1,res.wid=8;
  34. res.mt[1][1]=1,res.mt[1][2]=res.mt[1][3]=res.mt[1][4]=res.mt[1][5]=res.mt[1][6]=res.mt[1][7]=res.mt[1][8]=0;
  35. while(b){
  36. if(b&1)
  37. res=mul(res,a);
  38. a=mul(a,a),b>>=1;
  39. }
  40. return res.mt[1][5];
  41. }
  42. int main(){
  43. scanf("%d",&n);
  44. printf("%d\n",calc(n));
  45. return 0;
  46. }

然后就AC了!

复杂度:,带一个的大常数。

正确性证明

那,为什么这是对的呢?

我们考虑上面构造的矩阵,看它的实际含义:

我们发现的含义是除了车站,车站是否可以到达车站,等价于除了车站,车站经过一条边(即换乘一次)是否能到达车站。因为里面的数由组成,所以等价于即除了车站,车站经过一条边到达车站的方案数。

那把它自乘一次(即平方一次)呢?

经过平方后,其意义其实就是除了车站,车站经过二条边到达车站的方案数。

原因是矩阵平方时的代码是这样的:

  1. matrix mul(matrix a,matrix b){
  2. matrix res;
  3. res.len=a.len,res.wid=b.wid;
  4. for(int i=1;i<=res.len;i++)
  5. for(int j=1;j<=res.wid;j++)
  6. res.mt[i][j]=0;
  7. for(int i=1;i<=a.len;i++)
  8. for(int j=1;j<=b.wid;j++)
  9. for(int k=1;k<=a.wid;k++)
  10. res.mt[i][j]=(res.mt[i][j]+a.mt[i][k]*b.mt[k][j]%mod)%mod;
  11. return res;
  12. }

即有

这可以看为一个变相的Floyd(矩阵加速Floyd会在后面进行讨论):枚举起点与终点,枚举决策点,使用乘法原理,利用的路径数量更新的路径数量。

再看看,它有,刚好是每一条从的长度为路径数量相乘之和。

以此类推,我们就知道这个矩阵的次方代表的是除了车站,每个车站经过条边到达其他车站的方案数。

这刚好与我们的题目不谋而合!

推广到一般情况,给定邻接矩阵次幂代表每个点经过条边到达其他车站的方案数,这样,我们就可以用矩阵快速幂实现许多黑科技!(这个结论可以用归纳法证明,留为作业

例题1:P4159 [SCOI2009] 迷路(拆点,普通矩阵加速)

现在,我们就可以做一道题目来实践刚刚的结论了!

题意:给定一个带边权的无向图,求从点到点,长度为的路径数量。

数据范围:,所有边权为内的整数。

分析:

这道题如果去掉边权就是我们上面讨论的结论了,现在考虑怎么加上边权。

可以拆点,将每个点拆做个状态(),第个状态的点用表示,代表还要经过个长度的边才能到达点的状态,这样就好办了。

这样,我们就可以直接上板子了:

  1. #include<stdio.h>
  2. #include<iostream>
  3. using namespace std;
  4. const int maxn=105,mod=2009;
  5. int i,j,k,m,n;
  6. string s;
  7. struct matrix{
  8. int len,wid;
  9. int mt[maxn][maxn];
  10. }st;
  11. inline int getnum(int x,int k){
  12. return (k-1)*n+x;
  13. }
  14. matrix mul(matrix a,matrix b){
  15. matrix res;
  16. res.len=a.len,res.wid=b.wid;
  17. for(int i=1;i<=res.len;i++)
  18. for(int j=1;j<=res.wid;j++)
  19. res.mt[i][j]=0;
  20. for(int i=1;i<=a.len;i++)
  21. for(int j=1;j<=b.wid;j++)
  22. for(int k=1;k<=a.wid;k++)
  23. res.mt[i][j]=(res.mt[i][j]+a.mt[i][k]*b.mt[k][j]%mod)%mod;
  24. return res;
  25. }
  26. int calc(int b){
  27. matrix a=st,res;
  28. res.len=1,res.wid=9*n;
  29. res.mt[1][getnum(1,1)]=1;
  30. for(int i=2;i<=9*n;i++)
  31. res.mt[1][i]=0;
  32. while(b){
  33. if(b&1)
  34. res=mul(res,a);
  35. a=mul(a,a),b>>=1;
  36. }
  37. return res.mt[1][getnum(n,1)];
  38. }
  39. int main(){
  40. scanf("%d%d",&n,&m);
  41. st.len=st.wid=9*n;
  42. for(i=1;i<=n;i++){
  43. cin>>s;
  44. for(j=0;j<n;j++)
  45. if(s[j]!='0')
  46. st.mt[getnum(i,1)][getnum(j+1,s[j]-48)]=1;
  47. }
  48. for(i=1;i<=n;i++)
  49. for(j=1;j<=8;j++)
  50. st.mt[getnum(i,j+1)][getnum(i,j)]=1;
  51. printf("%d\n",calc(m));
  52. return 0;
  53. }

例题2:P2151 [SDOI2009]HH去散步(点边互换,普通矩阵加速)

同样,我们再做一道比较新颖的题目来巩固一下:P2151 [SDOI2009]HH去散步

这道题运用了点边互换的小trick,分析详见P2151 [SDOI2009]HH去散步解题报告

例题3:CF576D Flights for Regular Customers(bitset优化矩阵乘法)

有时候,的复杂度不足以我们通过题目,我们以这一题为例,来了解bitset对矩阵乘法的优化。

题意:给定一张个点条边的有向图,起点为,终点为,第条边可以通行当且仅当已经走过条边,求起点到终点最少要走多少条边,若无法到达输出“Impossible”。

数据范围:

分析:

这道题比较经典,值得一做。

考虑边的解锁顺序,先套路地对边进行排序(按值从小到达)。然后每次添加进一条边,判断是否能到达,如果能到达则求答案最小值,否则输出“Impossible”。

考虑怎么添加边:假设已经枚举到了第条边,我们维护了一个矩阵表示当前的邻接矩阵(现在还没有更新),那么一定有矩阵,表示在解锁第条边后,走了条边后,能到达的点的邻接矩阵。然后我们加入第条边,更新,并用Floyd求数组,其中表示加入第条边后的最短路。

那么更新答案也比较简单了:因为我们需要经过条边才能解锁第条边,所以的边数是必须要的;在解锁了第条边后,如果我们在点,那么我们到达终点还需要的边数就是,已经用Floyd求出来了,因此我们答案就可以求出来了(注意,点可以更新答案的原因是可以在的步数内由上一个转移来,所以需要判断一下是否满足):

但即使我们使用了矩阵快速幂,我们的复杂度也是的,不足以通过本题(其中的最大值)。

因为我们复杂度的瓶颈在于求矩阵上,因此我们考虑如何优化矩阵快速幂,也就是矩阵乘法的复杂度。因为只需要判断是否可以到达,所以只需要存储数据就可以了。什么东西处理数据最快呢?bitset!因此我们可以用bitset维护矩阵,复杂度(注,如果不知道bitset可以看扶苏的bitset浅谈进行学习)

代码:

  1. #include<stdio.h>
  2. #include<bitset>
  3. #include<algorithm>
  4. #define inf 0x3f3f3f3f
  5. using namespace std;
  6. const int maxn=155,maxm=155;
  7. int i,j,k,m,n,t,ans;
  8. int dis[maxn][maxn];
  9. struct matrix{
  10. int len,wid;
  11. bitset<maxn>mt[maxn];
  12. }now,g;
  13. struct edge{
  14. int x,y,d;
  15. }e[maxm];
  16. inline int cmp(edge a,edge b){
  17. return a.d<b.d;
  18. }
  19. matrix mul(matrix a,matrix b){
  20. matrix res;
  21. res.len=a.len,res.wid=b.wid;
  22. for(int i=1;i<=res.len;i++)
  23. for(int j=1;j<=res.wid;j++)
  24. res.mt[i][j]=0;
  25. for(int k=1;k<=a.wid;k++)
  26. for(int i=1;i<=a.len;i++)
  27. if(a.mt[i][k])
  28. res.mt[i]|=b.mt[k];
  29. return res;
  30. }
  31. matrix calc(matrix a,int b,matrix res){
  32. while(b){
  33. if(b&1)
  34. res=mul(res,a);
  35. a=mul(a,a),b>>=1;
  36. }
  37. return res;
  38. }
  39. int main(){
  40. scanf("%d%d",&n,&m);
  41. for(i=1;i<=m;i++)
  42. scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].d);
  43. sort(e+1,e+1+m,cmp);
  44. ans=inf;
  45. now.len=n,now.wid=n;
  46. for(i=1;i<=n;i++)
  47. now.mt[i][i]=1;
  48. g.len=n,g.wid=n;
  49. for(t=1;t<=m;t++){
  50. now=calc(g,e[t].d-e[t-1].d,now);
  51. g.mt[e[t].x][e[t].y]=1;
  52. for(i=1;i<=n;i++)
  53. for(j=1;j<=n;j++){
  54. if(g.mt[i][j]==0)
  55. dis[i][j]=i==j? 0:inf;
  56. else dis[i][j]=g.mt[i][j];
  57. }
  58. for(k=1;k<=n;k++)
  59. for(i=1;i<=n;i++)
  60. for(j=1;j<=n;j++)
  61. dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
  62. for(i=1;i<=n;i++)
  63. if(now.mt[1][i])
  64. ans=min(ans,e[t].d+dis[i][n]);
  65. }
  66. if(ans==inf)
  67. puts("Impossible");
  68. else printf("%d\n",ans);
  69. return 0;
  70. }

例题4:P2579 [ZJOI2005]沼泽鳄鱼(分组矩阵加速)

有时候,单个的矩阵已经无法满足我们表示状态的需求了,此时我们需要处理多个矩阵,并同样地利用这些矩阵进行加速,我们以P2579 [ZJOI2005]沼泽鳄鱼为例。

题意:给定一个个点条边的无向图,其中会有一些食人鱼按照,或,或的顺序进行行动(每次行动时间需要单位时间),每条边需要花费单位时间,求从,长度为,且到的每个点都没有食人鱼的路径数量。

数据范围:,食人鱼的数量不超过

分析:

如果不考虑食人鱼,这就是一个显然的矩阵快速幂题,但是有了食人鱼的限制,我们就需要把一些点禁掉,不可以到某些点。

但是,食人鱼会动啊?这怎么处理呢?

其实比较简单,我们发现食人鱼的运动周期比较小,只有,这样,我们就可以枚举每一个状态(最多种状态),处理这个状态时的邻接矩阵,然后判断一下的大小:如果小于就暴力乘,如果大于等于就计算有几个循环,用个状态相乘得到的矩阵来矩阵快速幂,余数暴力乘。

有一点需要注意的是,由于矩阵乘法不满足交换律,所以我们在把个状态相乘的时候,需要按的顺序顺次相乘(因为时间还没有出发)。

代码:

  1. #include<stdio.h>
  2. const int maxn=55,mod=10000;
  3. int i,j,k,l,m,n,s,t,p,fish;
  4. struct matrix{
  5. int len,wid;
  6. int mt[maxn][maxn];
  7. }r[13];
  8. matrix mul(matrix a,matrix b){
  9. matrix res;
  10. res.len=a.len,res.wid=b.wid;
  11. for(int i=1;i<=res.len;i++)
  12. for(int j=1;j<=res.wid;j++)
  13. res.mt[i][j]=0;
  14. for(int i=1;i<=a.len;i++)
  15. for(int j=1;j<=b.wid;j++)
  16. for(int k=1;k<=a.wid;k++)
  17. res.mt[i][j]=(res.mt[i][j]+a.mt[i][k]*b.mt[k][j]%mod)%mod;
  18. return res;
  19. }
  20. int calc(int x,int y,int b){
  21. int rst=b%12;
  22. matrix a,res;
  23. res.len=1,res.wid=n;
  24. for(int i=1;i<=n;i++)
  25. res.mt[1][i]=0;
  26. res.mt[1][x]=1;
  27. if(b<12){
  28. for(int i=2;i<=b+1;i++)
  29. res=mul(res,r[i]);
  30. return res.mt[1][y];
  31. }
  32. a=r[2];
  33. for(int i=3;i<=12;i++)
  34. a=mul(a,r[i]);
  35. a=mul(a,r[1]);
  36. b/=12;
  37. while(b){
  38. if(b&1)
  39. res=mul(res,a);
  40. a=mul(a,a),b>>=1;
  41. }
  42. for(int i=2;i<=rst+1;i++)
  43. res=mul(res,r[i]);
  44. return res.mt[1][y];
  45. }
  46. int main(){
  47. scanf("%d%d%d%d%d",&n,&m,&s,&t,&p);
  48. s++,t++;
  49. for(i=1;i<=12;i++)
  50. r[i].len=n,r[i].wid=n;
  51. for(i=1;i<=m;i++){
  52. int x,y;
  53. scanf("%d%d",&x,&y);
  54. x++,y++;
  55. for(j=1;j<=12;j++)
  56. r[j].mt[x][y]=r[j].mt[y][x]=1;
  57. }
  58. scanf("%d",&fish);
  59. for(i=1;i<=fish;i++){
  60. int x,y;
  61. scanf("%d",&x);
  62. for(j=1;j<=x;j++){
  63. scanf("%d",&y);
  64. y++;
  65. for(k=j;k<=12;k+=x)
  66. for(l=1;l<=n;l++)
  67. r[k].mt[l][y]=0;
  68. }
  69. }
  70. printf("%d\n",calc(s,t,p));
  71. return 0;
  72. }

例题5:P2886 [USACO07NOV]Cow Relays G(矩阵乘法重定义)

上文说过,会对矩阵加速Floyd进行讨论,我们便以此题为例来探讨矩阵乘法重定义后对Floyd的加速

我们以这道题为例子,来了解如何对矩阵乘法重定义,并证明矩阵加速的正确性。

题意:给定一张条边的无向图,求经过条边的最短路径长度。

数据范围:

分析:

我们可以先想一想这道题的暴力解法,虽然复杂度不能通过本题,但可以对正解产生一定的启发:

我们可以直接建图,设然后做遍Floyd,每次Floyd用更新最短路长度

(注,文中的Floyd可能枚举顺序与大部分的Floyd顺序不一样,但是仍然是正确的)

  1. for(i=1;i<=n;i++)
  2. for(j=1;j<=n;j++)
  3. dis[i][j]=i==j? 0:inf;
  4. for(p=1;p<=q;p++){
  5. for(i=1;i<=n;i++)
  6. for(j=1;j<=n;j++)
  7. tmp[i][j]=inf;
  8. for(i=1;i<=n;i++)
  9. for(j=1;j<=n;j++)
  10. for(k=1;k<=n;k++)
  11. tmp[i][j]=min(tmp[i][j],dis[i][k]+g[k][j]);
  12. for(i=1;i<=n;i++)
  13. for(j=1;j<=n;j++)
  14. dis[i][j]=tmp[i][j];
  15. }

(暴力比正解还难写)

其实,Floyd中核心内容就一句话:

而矩阵乘法的核心是这样:

这两个式子惊人的相似,我们考虑是否可以把矩阵乘法重定义,实现Floyd的转移,同时也要满足结合律(为了让矩阵快速幂可以对其加速)。

我们定义矩阵乘法为:

我们证明一下这个矩阵乘法满足结合律:

,那么有,因为外层的要让最小,里层的要让最小,所以可以直接拆开括号,得到

,同理有

因此,我们证明了这个矩阵乘法满足结合律,这样就可以利用矩阵快速幂对邻接矩阵的Floyd转移进行加速了!

时间复杂度是,可以通过本题。

代码:

  1. #include<stdio.h>
  2. #include<map>
  3. #define inf 1000000000
  4. using namespace std;
  5. const int maxn=105;
  6. int i,j,k,m,n,s,t,tot;
  7. map<int,int>mp;
  8. struct matrix{
  9. int len,wid;
  10. int mt[maxn][maxn];
  11. }st;
  12. matrix _mul(matrix a,matrix b){
  13. matrix res;
  14. res.len=a.len,res.wid=b.wid;
  15. for(int i=1;i<=res.len;i++)
  16. for(int j=1;j<=res.wid;j++)
  17. res.mt[i][j]=inf;
  18. for(int i=1;i<=a.len;i++)
  19. for(int j=1;j<=b.wid;j++)
  20. for(int k=1;k<=a.wid;k++)
  21. res.mt[i][j]=min(res.mt[i][j],a.mt[i][k]+b.mt[k][j]);
  22. return res;
  23. }
  24. int calc(int x,int y,int b){
  25. matrix a=st,res;
  26. res.len=1,res.wid=n;
  27. for(int i=1;i<=n;i++)
  28. res.mt[1][i]=inf;
  29. res.mt[1][x]=0;
  30. while(b){
  31. if(b&1)
  32. res=_mul(res,a);
  33. a=_mul(a,a),b>>=1;
  34. }
  35. return res.mt[1][y];
  36. }
  37. int main(){
  38. scanf("%d%d%d%d",&k,&m,&s,&t);
  39. for(i=1;i<=m;i++){
  40. int x,y,z;
  41. scanf("%d%d%d",&z,&x,&y);
  42. if(mp.count(x)==0)
  43. mp[x]=++tot;
  44. if(mp.count(y)==0)
  45. mp[y]=++tot;
  46. x=mp[x],y=mp[y];
  47. st.mt[x][y]=st.mt[y][x]=z;
  48. }
  49. s=mp[s],t=mp[t],n=tot;
  50. st.len=n,st.wid=n;
  51. for(i=1;i<=n;i++)
  52. for(j=1;j<=n;j++)
  53. if(st.mt[i][j]==0)
  54. st.mt[i][j]=inf;
  55. printf("%d\n",calc(s,t,k));
  56. return 0;
  57. }

例题6:P6569 [NOI Online #3 提高组]魔法值

在这里,我们来根据P6569 [NOI Online #3 提高组]魔法值谈谈矩阵乘法的倍增预处理与二进制拆分优化。

题意:给定一个个点,条边的无向图(带点权),每一天,每个节点的点权会异或上与它相邻的所有点昨天的点权,次询问,每次询问节点在第天的点权。

看到这道题目,很显然我们可以重定义一下矩阵乘法:

然后,我们可以让矩阵为原点权矩阵(大小),矩阵为邻接矩阵(大小),此时矩阵就是下一天的点权。

然后我们每一次询问做一次矩阵快速幂可以做到的复杂度。

但是这个复杂度不能通过本题,需要考虑优化。

我们发现做矩阵快速幂的时候我们的矩阵大小都是的,十分浪费,而我们最后的答案只需要求节点的点权,应该可以省去很多不需要求的信息。

我们预处理所有次幂的邻接矩阵,然后在询问的时候对于进行二进制拆分,乘上所有对应的邻接矩阵。

但这样的复杂度为什么是对的呢?因为询问的时候进行的矩阵乘法是矩阵的乘法,而根据矩阵乘法的过程可以知道它的复杂度为

这样,时间复杂度就优化到了:

  1. #include<stdio.h>
  2. #include<string.h>
  3. #define int long long
  4. const int maxn=105,maxk=40;
  5. int n,m,q;
  6. int s[maxn];
  7. struct matrix{
  8. int len,wid;
  9. int mt[maxn][maxn];
  10. }zero,ans[maxk];
  11. matrix mul(matrix a,matrix b){
  12. matrix res=zero;
  13. res.len=a.len,res.wid=b.wid;
  14. for(int i=1;i<=a.len;i++)
  15. for(int j=1;j<=b.wid;j++)
  16. for(int k=1;k<=a.wid;k++)
  17. res.mt[i][j]^=(a.mt[i][k]*b.mt[k][j]);
  18. return res;
  19. }
  20. int query(int x){
  21. matrix res;
  22. res.len=1,res.wid=n;
  23. for(int i=1;i<=n;i++)
  24. res.mt[1][i]=s[i];
  25. for(int i=0;i<maxk;i++)
  26. if((x>>i)&1)
  27. res=mul(res,ans[i]);
  28. return res.mt[1][1];
  29. }
  30. signed main(){
  31. zero.len=zero.wid=0;
  32. memset(zero.mt,0,sizeof(zero.mt));
  33. scanf("%lld%lld%lld",&n,&m,&q);
  34. for(int i=1;i<=n;i++)
  35. scanf("%lld",&s[i]);
  36. ans[0].len=ans[0].wid=n;
  37. for(int i=1;i<=n;i++)
  38. for(int j=1;j<=n;j++)
  39. ans[0].mt[i][j]=0;
  40. for(int i=1;i<=m;i++){
  41. int x,y;
  42. scanf("%lld%lld",&x,&y);
  43. ans[0].mt[x][y]=ans[0].mt[y][x]=1;
  44. }
  45. for(int i=1;i<maxk;i++)
  46. ans[i]=mul(ans[i-1],ans[i-1]);
  47. for(int i=1;i<=q;i++){
  48. int x;
  49. scanf("%lld",&x);
  50. printf("%lld\n",query(x));
  51. }
  52. return 0;
  53. }

终极挑战:P6772 [NOI2020]美食家

学了矩阵加速图上问题,结果同步赛写挂了,只有分,沦为暴力分/kk。

这道题是矩阵加速图上问题的一些技巧综合题,前置知识:拆点,矩阵乘法转向量乘法,即P4159 [SCOI2009] 迷路P6569 [NOI Online #3 提高组]魔法值

加油,在前面的学习中,你已经锻炼了基本的矩阵乘法思维,现在是实践的时候了!

总结

矩阵加速是一个比较神奇的科技,复杂度是(其中为矩阵的边长,为题目中询问的值,常为时间,距离等)。

因此,矩阵加速图上问题的题目大多会有这样的规律:图的点数很小,或者边数很小,同时非常大,这个时候你基本上就可以确定使用矩阵快速幂了。

再加上矩阵乘法可以有重定义,bitset优化等黑科技,所以矩阵加速图上问题的应用其实非常广(但是我太菜了,并不知道很多应用),希望大家可以探索出更多的毒瘤操作,危害社会(狗头)。

其他

这里给出所有本人已知的此类型题目,有兴趣的读者可以随意切:

参考文献:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注