多校三第M题,题意是给你n个点,m条带权的有向边,有q个询问,每次询问 s t k 表示从 s 到 t 至少要走 k 次的最短路。
思路:设d1[ k ][ i ][ j ]为从 i 到 j 走了k步的最短路,那么转移方程很简单就是 d1[ k ][ i ][ j ]=min( d1[ k-1 ][ i ][ p ]+g[ p ][ j ] ),但是k最大有1e4,复杂度1e4*n^3,太大,得知n最大为50,那么任意两点最短路走过的边的次数肯定不超过50,于是可以分块,将1e4分为100块,每块为100,设d2[ k ][ i ][ j ]为恰好走了k百步从 i 到 j 的最短路,显然d2[ k ][ i ][ j ]=min( d2[ k-1 ][ i ][ p ]+d1[100][ p ][ j ] ) ,设A=k%100,B=k/100,那答案就是min( d1[A][ s ][ i ]+d2[ B ][ i ][ t ]) 吗,显然不是,有没有可能从 s 到 t 走了100多步比走100步更短了,完全有可能,因此于是排除情况,设d3[ k ][ i ][ j ]为至少走了k百步从 i 到 j 的最短路,再设d[ i ][ j ],为任意两点的最短路,先用弗洛伊德算法搞定 d 数组,显然,d3[ k ][ i ][ j ]=min( d2[ k ][ i ][ p ]+d[ p ][ j ] ),那么就搞定这题啦。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=1e8;
const int N=55,M=105;
int d1[M][N][N],d2[M][N][N],d3[M][N][N];
int n,d[N][N],g[N][N];
void ss(int a[][N],int b[][N],int c[][N])
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
c[i][j]=inf;
for(int k=1;k<=n;k++)
c[i][j]=min(c[i][j],a[i][k]+b[k][j]);
}
}
void floyd()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j]=i==j?0:g[i][j]; //初始化d
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]);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int m,u,v,w,q;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=inf;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
g[u][v]=min(g[u][v],w);//可能有重边
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
d1[0][i][j]=d2[0][i][j]=i==j?0:inf;
for(int k=0;k<M;k++)
d3[k][i][j]=inf;
}
for(int i=1;i<M;i++)
ss(d1[i-1],g,d1[i]);//由只走i-1次的任意两点最短路推走i次的任意两点的最短路
for(int i=1;i<M;i++)
ss(d2[i-1],d1[100],d2[i]);//同递推
floyd();//弗洛伊德算法求任意两点的最短路
for(int x=0;x<M;x++)//刚好走100步从u到v的最短路不一定是最优
ss(d2[x],d,d3[x]);
scanf("%d",&q);
while(q--)
{
scanf("%d%d%d",&u,&v,&w);
int A=w%100,B=w/100;
int ans=inf;
for(int i=1;i<=n;i++)
ans=min(ans,d1[A][u][i]+d3[B][i][v]);
printf("%d\n",ans==inf?-1:ans);
}
}
}