![洛谷P3758/BZOJ4887 [TJOI2017] 可乐 [矩阵快速幂] 洛谷P3758/BZOJ4887 [TJOI2017] 可乐 [矩阵快速幂]](https://image.shishitao.com:8440/aHR0cHM6Ly9ia3FzaW1nLmlrYWZhbi5jb20vdXBsb2FkL2NoYXRncHQtcy5wbmc%2FIQ%3D%3D.png?!?w=700)
可乐
Time Limit: 10 Sec Memory Limit: 64 MB
Submit: 299 Solved: 207
Description
Input
Output
输出可乐机器人的行为方案数,答案可能很大,请输出对2017取模后的结果。
Sample Input
1 2
2 3
2
Sample Output
HINT
Source
分析:
一道人类智慧题,思路无比妙。
大多数人第一眼看到这题反应应该都是$DP$,出题人貌似也没想卡一般的$DP$(用$DP$+矩阵加速也是可以以非常优秀的效率过掉的),因此写的好看的$DP$也可以过,不过可能会需要吸一口氧气。
正解是矩阵快速幂。
嗯???矩阵快速幂??这题和矩阵快速幂有关系??是的,正解就是矩阵快速幂。juruo一开始也没想到,还是看了一位julao的思路才豁然开朗。
首先看,$n$的范围只有$30$,明显可以用邻接矩阵。而这题的突破口就在这里。我们来思考,如果对邻接矩阵$A$做快速幂会怎样?
从$Floyd$算法的角度分析,没有边权时(即默认所有边的边权为$1$时),$A^k$中的任意一个元$a_{i,j}$表示从$i$到$j$经过$k$条边的方案数。这个不太方便字面上解释,可以自己根据矩阵乘法的法则结合具体例子分析一下。
对于这题,因为可以留在原地,所以我们可以把所有点都加上一个自环。还有爆炸的情况,我们可以把爆炸当作第$0$号点,并单方面建立所有点到$0$号点的单向边,这样的过的话到达$0$号点以后就不会再到其他点,就能表示出爆炸的情况了。最后统计的答案应该就是从$1$号点到所有点经过$k$条边的情况总和,也就是$\sum ^n_{i=0}a[1][i]$。
剩下的就是矩阵快速幂的模板了。
不得不说,真是一道人类智慧题。
Code:
//It is made by HolseLee on 6th Sep 2018
//Lougu.org P3758
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std; const int mod=;
int n,m,t,ans;
struct Matrix {
int a[][];
Matrix() { memset(a,,sizeof(a)); }
Matrix(int b[][]) { memcpy(a,b,sizeof(a)); }
friend Matrix operator * (const Matrix x,const Matrix y) {
Matrix ret;
for(int i=; i<=; ++i)
for(int j=; j<=; ++j)
for(int k=; k<=; ++k) {
ret.a[i][j]=(ret.a[i][j]+(x.a[i][k]*y.a[k][j]))%mod;
}
return ret;
}
}H,L; int main()
{
scanf("%d%d",&n,&m);
int x,y;
for(int i=; i<=m; ++i) {
scanf("%d%d",&x,&y);
H.a[x][y]=H.a[y][x]=;
}
for(int i=; i<=n; ++i) H.a[i][]=, H.a[i][i]=;
for(int i=; i<=n; ++i) L.a[i][i]=;
scanf("%d",&t);
while( t ) {
if( t& ) L=L*H;
t>>=; H=H*H;
}
for(int i=; i<=n; ++i)
ans=(ans+L.a[][i])%mod;
printf("%d",ans);
return ;
}