【BZOJ1875】[SDOI2009] HH去散步(矩乘)

点此看题面

大致题意: 给定一张无向图,让你从起点出发,每次不能重走上次的边(两点之间可能有重边),问走\(k\)步到终点的方案数。

矩乘

考虑矩乘。

一开始\(naive\)想要直接记第\(i\)行第\(j\)列为从\(i\)号点转移到\(j\)号点的方案数,由于有不能重走上次的边这一限制,无法直接转移。

然后就开始考虑,记一个点对\((x,y)\)表示从第\(y\)条边走到\(x\)号点这一状态,然后记第\(i\)行第\(j\)列为从第\(i\)个状态转移到第\(j\)个状态的方案数,由于方案数总数\(nm\),直接炸飞。

结果突然发现,确定了\(y\)自然就确定了\(x\)

于是,只要记第\(i\)行第\(j\)列为从\(i\)号边转移到\(j\)号边的方案数,就可以了。

至于不能重走上次的边,套路地把一条无向边拆成两条有向边,规定一条边拆成的两条边不能相互转移即可。

代码

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 120
#define X 45989
#define LL long long
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,k,s,t;struct line {int x,y;}e[N+5];
struct M//矩乘
{
	LL a[N+5][N+5];I M() {memset(a,0,sizeof(a));}I LL* operator [] (CI x) {return a[x];}
	I M operator * (Con M& o) Con
	{
		M t;RI i,j,k;for(i=1;i<=m;++i) for(j=1;j<=m;++j)
			for(k=1;k<=m;++k) t[i][j]=a[i][k]*o.a[k][j]+t[i][j];
		for(i=1;i<=m;++i) for(j=1;j<=m;++j) t[i][j]%=X;return t;
	}
	I M operator ^ (RI y) Con
	{
		M x=*this,t;for(RI i=1;i<=m;++i) t[i][i]=1;W(y) y&1&&(t=t*x,0),x=x*x,y>>=1;return t;
	}
}U;
int main()
{
	RI i,j;for(scanf("%d%d%d%d%d",&n,&m,&k,&s,&t),i=1;i<=m;++i)
		scanf("%d%d",&e[i].x,&e[i].y),e[m+i].x=e[i].y,e[m+i].y=e[i].x;m<<=1;//读入边,拆两条
	for(i=1;i<=m;++i) for(j=1;j<=m;++j) e[i].y==e[j].x&&abs(i-j)^(m>>1)&&(U[i][j]=1);//注意不能是同一条边拆成的
	LL g=0;for(U=U^(k-1),i=1;i<=m;++i) for(j=1;j<=m;++j) e[i].x==s&&e[j].y==t&&(g+=U[i][j]);//根据快速幂后的矩阵计算答案
	return printf("%d",g%X),0;//输出答案
}
posted @ 2020-06-09 18:56  TheLostWeak  阅读(13)  评论(0编辑  收藏