【ybtoj高效进阶6-1-3】【luogu P3758】行为方案 / 可乐
洛谷的数据加强版 :【luogu P5789】 可乐(数据加强版)
题目大意:
给你一个图,一开始你处于1号点,每一秒钟你可以干三件事:
(1):通过某些连边走往另一个点;
(2):呆在原地不动;
(3):在原地爆炸。
问过了 t 秒后你的行为方案总数是多少?
思路:
我们把呆在原地不动视为走了一个自环,原地爆炸看作走向了0号点。
考虑进行动态规划,
设
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示从 i 点到 j 点走了 k 步后的方案。
根据乘法原理0,那么我们有
f
[
i
]
[
j
]
[
k
]
=
∑
f
[
i
]
[
x
]
[
k
−
1
]
∗
f
[
x
]
[
j
]
[
1
]
f[i][j][k]=\sum f[i][x][k-1]*f[x][j][1]
f[i][j][k]=∑f[i][x][k−1]∗f[x][j][1] 其中 x 是 另一个可行点。
然后我们发现第三维只和 k-1和 1 有关,可以省略。
于是方程就变成了:
f
[
i
]
[
j
]
=
∑
f
[
i
]
[
x
]
∗
f
[
x
]
[
j
]
f[i][j]=\sum f[i][x]*f[x][j]
f[i][j]=∑f[i][x]∗f[x][j]
初始条件为:
f
[
i
]
[
i
]
=
1
,
f
[
i
]
[
0
]
=
1
f[i][i]=1,f[i][0]=1
f[i][i]=1,f[i][0]=1
这就不会爆空间啦。
接着分析,我们发现这个方程一共要转移 t 次,再仔细看一下
t
<
=
1
0
9
t<=10^9
t<=109
暴力转移方程要等到天荒地老······ 于是我们再分析一下转移方程
有没有发现这个玩意很像矩阵乘法的运算法则?
那么转移 t 次就是将初始的矩阵乘方 t 次 (矩阵的两个下标是0 ~n * 0 ~ n),得到的结果就是答案。
矩阵快速幂,开!!!
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#define r register
#define rep(i,x,y) for(r ll i=x;i<=y;++i)
#define per(i,x,y) for(r ll i=x;i>=y;--i)
using namespace std;
typedef long long ll;
const ll V=110,p=2017;
ll n,m,x,y,ans,t;
struct node
{
ll a[V][V];
ll n,m;
}a;
node operator *(const node &a,const node &b)
{
node c;
c.n=a.n,c.m=b.m;
memset(c.a,0,sizeof(c.a));
rep(k,0,a.m)
rep(i,0,c.n)
rep(j,0,c.m)
c.a[i][j]=(c.a[i][j]+(a.a[i][k]*b.a[k][j])%p)%p;
return c;
}
node makeone(ll n)
{
node x;
x.n=x.m=n;
rep(i,0,n)
rep(j,0,n)
if(i==j) x.a[i][j]=1;
else x.a[i][j]=0;
return x;
}
node power(node x,ll y)
{
node res=makeone(x.n);
while(y)
{
if(y&1) res=res*x;
x=x*x;
y>>=1;
}
return res;
}
int main()
{
scanf("%lld%lld",&n,&m);
rep(i,1,m)
{
scanf("%lld%lld",&x,&y);
a.a[x][y]=a.a[y][x]=1;
}
rep(i,0,n)
{
a.a[i][i]=1;
a.a[i][0]=1;
}
a.m=a.n=n;
scanf("%lld",&t);
node res=power(a,t);
rep(i,0,n) ans=(ans+res.a[1][i])%p;
cout<<ans;
return 0;
}