题目地址:http://www.spoj.com/problems/IE2/
题目大意:一个n个点m条边的无向图,n个点编号为1~n,走d步,要求经过编号为1~k的点至少一次,问方案数。n<=20,k<=7,d<=10^9。
算法讨论:
对必须经过的点进行容斥,强制某些点不能经过,删去和这些点相连的边,然后用矩阵乘法快速幂。
设f[i][j]表示经过j步,到达点i的方案数。转移方程为:f[i][j]+=f[i'][j-1](i和i'有边相连),因此转移矩阵即邻接矩阵,答案矩阵初始为{1,1,1,...,1,1}。
Code:
#include <cstdio>
#define N 20
#define mod 1000000009
using namespace std;
bool v[N+10],map[N+10][N+10];
int n,m,x,y,c,d,res,ans[N+10],tans[N+10],mat[N+10][N+10],tmat[N+10][N+10];
void dfs(int now,int cnt){
if (now>c){
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j) mat[i][j]=map[i][j];
for (int i=1;i<=n;++i)
if (v[i])
for (int j=1;j<=n;++j) mat[i][j]=mat[j][i]=0;
for (int i=1;i<=n;++i) ans[i]=1;
for (int td=d;td;td>>=1){
if (td&1){
for (int i=1;i<=n;++i) tans[i]=0;
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j) tans[i]=(tans[i]+(long long)ans[j]*mat[j][i])%mod;
for (int i=1;i<=n;++i) ans[i]=tans[i];
}
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j) tmat[i][j]=0;
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j)
for (int k=1;k<=n;++k) tmat[i][j]=(tmat[i][j]+(long long)mat[i][k]*mat[k][j])%mod;
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j) mat[i][j]=tmat[i][j];
}
if (cnt&1) for (int i=1;i<=n;++i) res=(res-ans[i]+mod)%mod;
else for (int i=1;i<=n;++i) res=(res+ans[i])%mod;
return;
}
v[now]=0;
dfs(now+1,cnt);
v[now]=1;
dfs(now+1,cnt+1);
}
int main(){
scanf("%d%d%d%d",&n,&m,&c,&d);
d--;
if (!d){printf("%d\n",n);return 0;}
for (int i=1;i<=m;++i) scanf("%d%d",&x,&y),map[x][y]=map[y][x]=1;
dfs(1,0);
printf("%d\n",res);
return 0;
}
By Charlie Pan
Aug 25,2014