【bzoj1875】[SDOI2009]HH去散步 矩阵乘法
题目描述
一张N个点M条边的无向图,从A走到B,要求:每一次不能立刻沿着上一次的边的反方向返回。求方案数。
输入
第一行:五个整数N,M,t,A,B。
N表示学校里的路口的个数
M表示学校里的路的条数
t表示HH想要散步的距离
A表示散步的出发点
B则表示散步的终点。
接下来M行
每行一组Ai,Bi,表示从路口Ai到路口Bi有一条路。
数据保证Ai != Bi,但不保证任意两个路口之间至多只有一条路相连接。
路口编号从0到N -1。
同一行内所有数据均由一个空格隔开,行首行尾没有多余空格。没有多余空行。
答案模45989。
N ≤ 20,M ≤ 60,t ≤ 2^30,0 ≤ A,B
输出
一行,表示答案。
样例输入
4 5 3 0 0
0 1
0 2
0 3
2 1
3 2
样例输出
4
题解
矩阵乘法
“每一次不能立刻沿着上一次的边的反方向返回”这个条件的限制导致不能直接进行矩阵乘法。
由于边数很少,我们可以换个思路:把边看作矩阵乘法的行列,矩阵中的元素表示 从一条边走到另一条边的方案数。
那么对于两条边$i,j$,如果不是刚走过来的边并且能够接上($y_i=x_j$),则$A_{ij}=1$。
然后建立两个虚点,第一个向起点为s的边连边,起点为t的项第二个连边。
然后快速幂矩阵乘法即可。
时间复杂度$O(m^3\log t)$
#include <cstdio> #include <cstring> #define N 130 #define mod 45989 int m , px[N] , py[N]; struct data { int v[N][N]; data() {memset(v , 0 , sizeof(v));} int* operator[](int a) {return v[a];} data operator*(data a) { data ans; int i , j , k; for(i = 0 ; i <= m ; i ++ ) for(j = 0 ; j <= m ; j ++ ) for(k = 0 ; k <= m ; k ++ ) ans[i][j] = (ans[i][j] + v[i][k] * a[k][j]) % mod; return ans; } }a; data pow(data x , int y) { data ans; int i; for(i = 0 ; i <= m ; i ++ ) ans[i][i] = 1; while(y) { if(y & 1) ans = ans * x; x = x * x , y >>= 1; } return ans; } int main() { int n , k , s , t , i , j; scanf("%d%d%d%d%d" , &n , &m , &k , &s , &t); for(i = 1 ; i <= m ; i ++ ) scanf("%d%d" , &px[i << 1] , &py[i << 1]) , px[i << 1 | 1] = py[i << 1] , py[i << 1 | 1] = px[i << 1]; m = m * 2 + 1; for(i = 2 ; i <= m ; i ++ ) { if(px[i] == s) a[0][i] = 1; if(py[i] == t) a[i][1] = 1; for(j = 2 ; j <= m ; j ++ ) if(py[i] == px[j] && (i ^ j) != 1) a[i][j] = 1; } a = pow(a , k + 1); printf("%d\n" , a[0][1]); return 0; }