洛谷P3758 - [TJOI2017]可乐
Description
给出一张\(n(n\leq30)\)个点\(m(m\leq100)\)条边的无向图。初始时有一个可乐机器人在点\(1\),这个机器人每秒会做出以下三种行为之一:原地不动,走向相邻点,自爆(自爆后就不能动了)。求经过\(t(t\leq10^6)\)秒后可乐机器人的行动方案数。
Solution
矩阵乘法优化DP。
首先改一下原图:每个点向自己连一条自环;建立一个点\(n+1\)表示自爆,其余每个点向\(n+1\)连一条有向边。然后原问题就简化成了在一个有向图上,每秒走向一个相邻的点。
\(dp[i][x]\)表示机器人在第\(i\)秒时在点\(x\)有多少种方案。做出邻接矩阵\(M\),则转移方程为:
\[ dp[i+1][v]=\sum_{u=1}^n \sum_{v=1}^n dp[i][u]M_{uv} $$ 是不是和矩阵乘法有点像?将$dp[i]$看做一个向量,则有$dp[i]=dp[i-1]\times M$。我们要求的是$dp[t]=dp[1]\times M^t$,那么只要做矩阵快速幂就好啦。
> 时间复杂度$O(n^3logt)$。
##Code
```cpp
//[TJOI2017]可乐
#include <cstdio>
#include <cstring>
inline char gc()
{
static char now[1<<16],*s,*t;
if(s==t) {t=(s=now)+fread(now,1,1<<16,stdin); if(s==t) return EOF;}
return *s++;
}
inline int read()
{
int x=0; char ch=gc();
while(ch<'0'||'9'<ch) ch=gc();
while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=gc();
return x;
}
const int P=2017;
const int N=40;
int n,m;
struct mtx
{
int row,col; int x[N][N];
mtx(int _row=0,int _col=0) {row=_row,col=_col; memset(x,0,sizeof x);}
friend mtx operator *(mtx A,mtx B)
{
mtx C=mtx(A.row,B.col);
for(int i=1;i<=A.row;i++)
for(int k=1;k<=A.col;k++)
for(int j=1;j<=B.col;j++)
C.x[i][j]=(C.x[i][j]+A.x[i][k]*B.x[k][j])%P;
return C;
}
};
mtx pow(mtx A,int k)
{
mtx r=mtx(A.row,A.col),t=A;
for(int i=1;i<=A.row;i++) r.x[i][i]=1;
for(int i=k;i;i>>=1,t=t*t) if(i&1) r=r*t;
return r;
}
int main()
{
n=read(),m=read(); mtx tr=mtx(n+1,n+1);
for(int i=1;i<=n+1;i++) tr.x[i][i]=1,tr.x[i][n+1]=1;
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
tr.x[u][v]=tr.x[v][u]=1;
}
int t=read(); mtx r=mtx(1,n+1);
r.x[1][1]=1; r=r*pow(tr,t);
int ans=0;
for(int i=1;i<=n+1;i++) ans=(ans+r.x[1][i])%P;
printf("%d\n",ans);
return 0;
}
```\]