【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;
}

 

posted @ 2017-09-27 17:24  GXZlegend  阅读(443)  评论(1编辑  收藏  举报