题解-[ZJOI2005]沼泽鳄鱼
前置知识:
邻接矩阵
矩阵乘法
矩阵快速幂
给一个有 (N) 个点,(M) 条双向边的图 (G),其中有 (NFish) 只鳄鱼以 (T) 个点 (P_0sim P_t) 为周期运动。求从 (Start) 出发到 (End) 不停留走 (K) 步每步不碰到鳄鱼的方案数(节点下标从 (0) 开始编号)。
数据范围:(1le Nle 50),(1le Kle 2 imes 10^9),(1le NFishle 20),(2le Tle 4)。
看到这个 (K) 的范围就知道要带个 (log),而在图上可以带 (log) 的算法,唯有二分、倍增(树形图)和邻接矩阵快速幂(小图)。看到这题 (N) 很小,又无从二分,便知道可以邻接矩阵快速幂。
首先要知道什么是矩阵乘法。大小为 (a imes b) 的矩阵 (A) 和大小为 (b imes c) 的矩阵方可相乘,乘积矩阵 (C) 大小为 (a imes c),满足 (C_{i,j}=sumlimits_{k=1}^bA_{i,k}cdot B_{k,j}(1le ile a,1le jle c))。
矩阵乘法可以解决集合的定向求和变换操作。其中原矩阵乘以变换矩阵变成目标矩阵。
矩阵快速幂:原矩阵多次定向变换,可以通过多次乘以变换矩阵解决。矩阵乘法满足结合律,所以可以先求出变换矩阵的幂。矩阵也可以快速幂,时间复杂度 (Theta(n^3log k))。
邻接矩阵:用于表示图边,在无权图中,如果矩阵 (M) 的元素 (M_{i,j}=1),表示存在边 ((i,j))。
而邻接矩阵快速幂就是建立在邻接矩阵和矩阵快速幂上的。原矩阵表示起点状态,变换矩阵为邻接矩阵,每乘一次就表示不停留地走一步后的状态,乘 (K) 次就成了最终状态,取终点矩阵值则为答案。
状态:到每个节点的方案数。
Example
讲解时暂时初始下标为 (1) 吧,暂时不考虑鳄鱼。
例如 (N=3),(Start=1),(End=3),有双向边 ({(1,3),(3,2)}),求走 (K=3) 步后到终点 (End) 的方案数。
原始状态:
变换矩阵为邻接矩阵:
目标状态:
所以到终点 (End=3) 的方案数为 (2)。
但是鳄鱼怎么办呢?
可以发现 (2le Tle 4),所以所有鳄鱼运动的总周期是 (12)。可以计算 (12) 种变换邻接矩阵,表示到每个周期时可以走的边。然后顺次相乘,求乘积的 (lfloorfrac K{12} floor) 次幂(可以用矩阵快速幂)乘以 前 (left(Kmod 12 ight)) 种变换矩阵的乘积,就是总变换矩阵。用表示起点的原矩阵乘以总变换矩阵,即可得答案。
时间复杂度 (Theta(NFish+N^3log K))(注意,(NFish) 为一个完整变量名)。
真的难讲,还是放代码吧。
Code
#include <bits/stdc++.h>
using namespace std;
//&Start
#define inf 0x3f3f3f3f
#define re register
#define il inline
#define hash unorded_map
typedef long long lng;
typedef unsigned long long ulng;
typedef vector<int> veci;
#define fo(i,st,xb,y) for(re int i=st;i xb;i y)
//&Data
#define N 50
#define mod 10000
int n,m,s,t,k,fish,p[25][12];
//&Matrix
struct Matrix{ //矩阵
int arr[N+5][N+5];
Matrix(re int op=0){
memset(arr,0,sizeof arr);
if(op==1) fo(i,1,<=N,++) arr[i][i]=1; //构造单位矩阵 E 满足 E*A=A*E=A
}
il int* operator[](re int x){return arr[x];}
il friend Matrix operator*(re Matrix x,re Matrix y){
re Matrix res;
fo(k,1,<=N,++)fo(i,1,<=N,++)fo(j,1,<=N,++)
(res[i][j]+=x[i][k]*y[k][j]%mod)%=mod;
return res;
}
il void print(re char*s){
puts(s);
fo(i,1,<=n,++)fo(j,1,<=n,++)
printf("%d%c",arr[i][j],"
"[j<n]);
}
}st,e,g[12],all,ans;
il Matrix Pow(re Matrix a,re int x){ //矩阵快速幂
re Matrix res(1);
for(;x;a=a*a,x>>=1)if(x&1) res=res*a;
return res;
}
//&Main
int main(){
scanf("%d%d%d%d%d",&n,&m,&s,&t,&k);
s++,t++,st[1][s]=1; //原矩阵
fo(i,1,<=m,++){
re int u,v;scanf("%d%d",&u,&v);
e[u+1][v+1]=e[v+1][u+1]=1; //邻接矩阵
}
scanf("%d",&fish);
fo(i,1,<=fish,++){
re int tmp; scanf("%d",&tmp);
fo(j,0,<tmp,++) scanf("%d",p[i]+j),p[i][j]++;
fo(j,tmp,<12,++) p[i][j]=p[i][j%tmp];
}
re int fb[N+5];
fo(i,0,<12,++){
fill(fb+1,fb+n+1,0);
fo(j,1,<=fish,++) fb[p[j][i]]=1;
fo(j,1,<=n,++)fo(k,1,<=n,++)
if(!fb[k]) g[i][j][k]=e[j][k]; //12种变换矩阵
}
all=Matrix(1);
fo(i,1,<12,++) all=all*g[i];
all=all*g[0]; //12个矩阵顺次相乘(1,2,...,11,0)
ans=st*Pow(all,k/12); //原矩阵乘以变换矩阵
fo(i,1,<=k%12,++) ans=ans*g[i]; //乘以剩余 K%12 个矩阵
printf("%d
",ans[1][t]); //答案
return 0;
}
祝大家学习愉快!