原题链接
简洁题意
求一个最短路,可以把中间至多 k k k条道路的权改成负的。
分析:
可以发现,本题 n n n的范围很小,可以用 f l o y d floyd floyd来做。 f l o y d floyd floyd求一边最短路, d ( i , j ) = min ( d ( i , k ) + d ( k , j ) ) d(i,j)=\min(d(i,k)+d(k,j)) d(i,j)=min(d(i,k)+d(k,j)),我们求出最短路后,考虑加上魔法。设 f ( i , j , p ) f(i,j,p) f(i,j,p)为 i i i到 j j j使用 p p p次魔法的最短路。可以把一条道路改成负的,那么就有状态转移方程 f ( i , j , p ) = min ( f ( i , u , x ) − e d g e ( u , v ) + f ( v , j , y ) , x + y = p ) f(i,j,p)=\min(f(i,u,x)-edge(u,v)+f(v,j,y),x+y=p) f(i,j,p)=min(f(i,u,x)−edge(u,v)+f(v,j,y),x+y=p)这样的状态转移方程空间是不够的,而且要枚举四层循环。如果改成滚动数组, f ( i , j ) f(i,j) f(i,j)表示 i i i到 j j j的最短路。不考虑用了几次魔法,则: f ( i , j ) = min ( f ( i , u ) − e d g e ( u , v ) + f ( v , j ) ) f(i,j)=\min(f(i,u)-edge(u,v)+f(v,j)) f(i,j)=min(f(i,u)−edge(u,v)+f(v,j))于是,我们来考虑用 k k k次魔法的问题。很明显,每次迭代都会选择一条边用魔法或者不选,而不选只会是最优解,则这样迭代 k k k次就是至多用 k k k次魔法的解。但是这样空间够了,可时间仍然不够,还是四层循环。
考虑再优化,这样计算了 k k k次,而且这个每次选了边用魔法,但是先选哪条边是没有关系的,所以满足结合律。那么满足结合律,就可以用快速幂解决。因为 f f f是二维数组,则可以用类似矩阵乘法的方法,改成求 min \min min即可。这样,先算出用一次魔法的方案,再快速幂算出用 k k k次的方案。
快速幂的时间复杂度是 Θ ( n 3 log n ) \Theta(n^3\log n) Θ(n3logn),前面 d p dp dp求最短路是 Θ ( n 3 ) \Theta(n^3) Θ(n3)的, d p dp dp求一次魔法的时间复杂度是 Θ ( n 2 m ) \Theta(n^2m) Θ(n2m)的,总的时间复杂度是 Θ ( n 3 log n + n 2 m ) \Theta(n^3\log n+n^2m) Θ(n3logn+n2m)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN=104;
int n,m,k;
ll d[NN][NN],f[NN][NN];
struct node
{
int u,v,w;
}edge[2504];
void mul(ll c[][NN],ll a[][NN],ll b[][NN])
{
ll t[NN][NN];
memset(t,0x3f,sizeof(t));
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
t[i][j]=min(t[i][j],a[i][k]+b[k][j]);
memcpy(c,t,sizeof(t));
}
ll ksm()
{
while(k)
{
if(k&1)
mul(d,d,f);
mul(f,f,f);
k>>=1;
}
return d[1][n];
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
memset(d,0x3f,sizeof(d));
for(int i=1;i<=n;i++)
d[i][i]=0;
for(int i=0;i<m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
edge[i]=(node){
u,v,w};
d[u][v]=min(d[u][v],1ll*w);
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
memcpy(f,d,sizeof(f));
for(int k=0;k<m;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]=min(f[i][j],d[i][edge[k].u]-edge[k].w+d[edge[k].v][j]);
printf("%lld",ksm());
return 0;
}