题目描述
加里敦星球的人们特别喜欢喝可乐。因而,他们的敌对星球研发出了一个可乐机器人,并且放在了加里敦星球的1号城市上。这个可乐机器人有三种行为: 停在原地,去下一个相邻的城市,自爆。它每一秒都会随机触发一种行为。现 在给加里敦星球城市图,在第0秒时可乐机器人在1号城市,问经过了t秒,可乐机器人的行为方案数是多少?
输入输出格式
输入格式:
第一行输入两个正整数况N,M,N表示城市个数,M表示道路个数。(1 <= N <=30,0 < M < 100)
接下来M行输入u,v,表示u,v之间有一条道路。(1<=u,v <= n)保证两座城市之间只有一条路相连。
最后输入入时间t
输出格式:
输出可乐机器人的行为方案数,答案可能很大,请输出对2017取模后的结果。
对于20%的pn,有1 < t ≤ 1000
对于100%的pn,有1 < t ≤ 10^6。
想法:
是一道很显然的dp题。转移方程分为爆炸和不爆炸两个。
f[0][i][j]表示在第i个时刻机器人在节点j上 并且已经爆炸了(可以是任何一个时间爆炸的)f[1][i][j]表示没有爆炸
f[0][i][j]=f[0][i-1][j]+f[1][i-1][j] 显然它要么是这个时刻自爆的 要么是已经爆了
f[1][i][j]=f[1][i-1][j]+f[1][i-1][k] 要么是上一步选择了原地不动 要么是从周围的某个点走过来的 其中k表示和j相邻的点
但是直接这样做不行 会MLE或者TLE。反正只有20分。所以要想办法优化。
有个好东西叫做矩阵快速幂。
我们可以把j和自己也想像成联通的 这样第二个式子就变成了f[1][i][j]=sum(f[1][i-1][k])。之后利用矩阵快速幂就可以直接算出最后一项的值。(相当于是原来那个图的邻接矩阵 再加上自己和自己的边)
之后再看第一个式子f[0][i][j]=f[0][i-2][j]+f[1][i-2][j]+f[1][i-1][j] 发现是一个等比数列求和。那么在刚才那个矩阵上的第n+1列全部填上1就好了。
最后的答案就是对结果矩阵的每个值求和。
代码:
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int p=2017;
int n,m,t;
struct matr
{
int x,y,sum;
long long m[35][35];
}a,b;
matr operator *(matr a,matr b)
{
matr c;
c.x=a.x;c.y=b.y;
c.sum=0;
for(int i=1;i<=c.x;i++)
{
for(int j=1;j<=c.y;j++)
{
c.m[i][j]=0;
for(int k=1;k<=a.x;k++)
{
c.m[i][j]+=(a.m[k][j]%p)*(b.m[i][k]%p);
c.m[i][j]%=p;
}
c.sum+=c.m[i][j];
c.sum%=2017;
}
}
return c;
}
int main()
{
scanf("%d%d",&n,&m);
a.x=n+1,a.y=1;
a.sum=b.sum=0;
a.m[1][1]=1;
b.x=b.y=n+1;
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
b.m[u][v]=1;
b.m[v][u]=1;
}
for(int i=1;i<=n+1;i++)
{
b.m[i][i]=1;
b.m[n+1][i]=1;
}
scanf("%d",&t);
while(t)
{
if(t&1) a=a*b;
b=b*b;
t=(t>>1);
}
printf("%d\n",a.sum);
return 0;
}