[SDOI2009]HH去散步 矩阵乘法
题解:
很久之前做的这道题,今天看差点没看懂QAQ,赶紧来记录一下。
考虑f[i][j]表示i和j是否右边相连,有为1,否则为0,那么f同时可以表示从每个点出发走一步到其他点的方案数。
于是用一个和f长得一模一样的矩阵g来表示从每个点出发到其他点的方案数。
那么考虑g如何转移。
其实只要用g*f就可以表示一次转移了。
为什么?
设当前转移到了第t次,则g[i][j]表示i到j走t-1次的方案数(因为还没有更新)
那么矩阵乘法做了什么?
$g[i][j] = \sum_{l =1}^{n}{g[i][l] * f[l][j]}$
也就是它枚举了点i走了t - 1次到l,然后再从l走一次到j的方案数。
是否能转移则要看l 到 j是否有边,而f[l][j]的意义刚好就是这样。
所以这其实就是做了一个DP,并用矩阵加速。
但是题目有限制,不能反复走一条边,但我们用点来表示状态显然无法对此做出限制,因此考虑直接用边来设状态。
g[i][j]表示从边i走到边j的方案数,一个边i可以走到j,当且仅当i的终点是j的起点。
为了满足题目的限制,我们需要将一条无向边拆分为两条有向边。然后满足上述条件则g[i][j] = 1。
然后当i是j的反向边时,g[i][j] = 0;
直接快速幂优化,获得最后的答案只需要再枚举一下哪些边可以到达终点即可
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 200 5 #define ACway 5000 6 #define mod 45989 7 int n,m,t,A,B,have,answer; 8 int date[ACway],Next[ACway],Head[AC],tot=1;//变tot为1开始以查询一条边的起点 9 /*ans:一行,若A可直达(边)则为1, 10 f:tot*tot 对于每列j而言,若行i满足:date[i](终点) == date[j^1](起点),则为1 11 have=t-1(第一次已经在ans上了) 12 因为是用边来转移和判断,所以最后要扫描一遍ans: 13 若此边可达B,则answer+=ans.s[1][i] 14 由于习惯问题,,,所有点一开始都+1*/ 15 16 struct matrix{ 17 int s[AC][AC]; 18 }ans,f,box; 19 20 inline int read() 21 { 22 int x=0;char c=getchar(); 23 while(c>'9' || c<'0') c=getchar(); 24 while(c>='0' && c<='9') x=x*10+c-'0',c=getchar(); 25 return x; 26 } 27 28 inline void add(int f,int w) 29 { 30 date[++tot]=w,Next[tot]=Head[f],Head[f]=tot; 31 date[++tot]=f,Next[tot]=Head[w],Head[w]=tot;//变无向为有向 32 } 33 34 void count(matrix x,matrix y) 35 { 36 for(R i=1;i<=tot;i++) 37 { 38 for(R j=1;j<=tot;j++) 39 { 40 box.s[i][j]=0; 41 for(R l=1;l<=tot;l++) 42 box.s[i][j]=(box.s[i][j] + x.s[i][l] * y.s[l][j])%mod; 43 } 44 } 45 } 46 47 void qpow() 48 { 49 while(have) 50 { 51 if(have & 1) 52 { 53 count(ans,f); 54 ans=box; 55 } 56 have>>=1; 57 count(f,f); 58 f=box; 59 } 60 } 61 62 void pre() 63 { 64 int a,b; 65 n=read(),m=read(),t=read(),A=read()+1,B=read()+1; 66 for(R i=1;i<=m;i++) 67 { 68 a=read()+1,b=read()+1; 69 add(a,b); 70 if(a == A)//如果A可以直达的话 71 ans.s[1][tot^1]=1; 72 else if(b == A) ans.s[1][tot]=1; 73 } 74 for(R j=2;j<=tot;j++)//枚举列 75 for(R i=2;i<=tot;i++)//枚举行 76 if(date[i] == date[j^1] && i != (j^1)) //error!!!这里要判断不是反向边才行,因为一个边的起点就等与反向边的终点啊 77 //{ 78 f.s[i][j]=1; 79 // printf("%d ---> %d\n",i,j); 80 //} 81 /* printf("ans:\n"); 82 for(R i=2;i<=tot;i++) 83 printf("%d ",ans.s[1][i]); 84 printf("\nf:\n"); 85 for(R i=2;i<=tot;i++) 86 { 87 for(R j=2;j<=tot;j++) 88 printf("%d ",f.s[i][j]); 89 printf("\n"); 90 }*/ 91 have=t-1; 92 } 93 94 void work() 95 { 96 for(R i=2;i<=tot;i++) 97 if(date[i] == B) answer+=ans.s[1][i]; 98 answer%=mod; 99 printf("%d\n",answer); 100 } 101 102 int main() 103 { 104 // freopen("in.in","r",stdin); 105 pre(); 106 qpow(); 107 work(); 108 // fclose(stdin); 109 return 0; 110 }