【bzoj4386】[POI2015]Wycieczki【矩阵快速幂】【倍增】
vjudge题目传送门
luogu题目传送门
题解
首先,我们考虑如何统计所有边权都是1的经过x条边的路径总数。很简单,构造转移矩阵我们只需要相邻的两个点u->v,(u,v)++,再设一个计数器代表路径总数,(u,计数器)++,最后再 (计数器,计数器)=1。初始矩阵就是(1,1)=(1,2)…=(1,n)=1。然后快速幂。
但是如果权值有2,3呢?蒟蒻从题解上get到一个很妙的想法:把每个点搞成3个,u1,u2,u3。
对于边(u,v)边权为1:
u1->v1
边权为2:
u1->u2->v1
边权为3:
u1->u2->u3->v1
现在明白是什么意思了吧!
这样每条边的权值都是1了。
现在我们可以很方便地求出经过x条边的路径总数。
接下来,有一个想法是二分。但是这里有一个更妙的想法:倍增。我们二进制拆分,从高位到低位统计答案。细节自己想一想吧。
生命中的第一个bzoj rank1,第二个luogu rank1!嗨森~
倒是没怎么卡。就是矩阵乘法改了一下枚举的顺序,0就直接跳过。纪念一下。
233 wyc大佬莫名乱入
代码
#include<cstdio>
#include<cstring>
typedef long long ll;
const int N=125;
int n,m,u,v,d,t;
ll k,s,bg[N][N],base[N][N],tmp[N][N],now[N][N],mul[64][N][N];
bool flag;
void times(ll a[][N],ll b[][N],ll c[][N]){
flag=true;
memset(tmp,0,sizeof(tmp));
for(int i=1;i<=3*n+1;i++){
for(int l=1;l<=3*n+1;l++){
if(!a[i][l]){
continue;
}
for(register int j=1;j<=3*n+1;j++){
tmp[i][j]+=a[i][l]*b[l][j];
}
if(i==1&&tmp[i][3*n+1]>=k){
flag=false;
}
}
}
for(int i=1;i<=3*n+1;i++){
for(int j=1;j<=3*n+1;j++){
c[i][j]=tmp[i][j];
}
}
}
int main(){
scanf("%d%d%lld",&n,&m,&k);
for(int i=1;i<=n;i++){
bg[1][i]=1;
base[i][i+n]=1;
base[i+n][i+2*n]=1;
}
while(m--){
scanf("%d%d%d",&u,&v,&d);
base[u+(d-1)*n][v]++;
base[u+(d-1)*n][3*n+1]++;
}
base[3*n+1][3*n+1]=1;
memcpy(mul[0],base,sizeof(base));
for(;t<=63;t++){
if(t){
times(mul[t-1],mul[t-1],mul[t]);
}
times(bg,mul[t],now);
if(!flag||now[1][3*n+1]>=k){
break;
}
}
t--;
if(t==63&&now[1][3*n+1]<k){
puts("-1");
return 0;
}
for(int i=t;i>=0;i--){
times(bg,mul[i],now);
if(flag&&now[1][3*n+1]<k){
s+=(1LL<<i);
memcpy(bg,now,sizeof(now));
}
}
printf("%lld\n",s+1);
return 0;
}