题意: 给出 BB 地区的村庄数 NN,村庄编号从 00N1N-1,和所有 MM 条公路的长度,公路是双向的。并给出第 ii 个村庄重建完成的时间 tit_i,你可以认为是同时开始重建并在第 tit_i 天重建完成,并且在当天即可通车。若 tit_i00 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 QQ 个询问 (x,y,t)(x, y, t),对于每个询问你要回答在第 tt 天,从村庄 xx 到村庄 yy 的最短路径长度为多少。如果无法找到从 xx 村庄到 yy 村庄的路径,经过若干个已重建完成的村庄,或者村庄 xx 或村庄 yy 在第 tt 天仍未重建完成 ,则需要返回 1-1

要做这道题,必须把 Floyd 算法的本质搞懂。

首先先来看看 Floyd 的代码,极其简洁:

for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(dis[i][j]>dis[i][k]+dis[k][j])
                dis[i][j]=dis[i][k]+dis[k][j];

可以观察出,Floyd 是通过枚举中转点,使两点之间的距离不断更新,当找到一个合适的中转点时,两点之间的距离便达到最短。

由于中转点的枚举 k 处在最外层循环,所以我们每次只能借助前 k 个中转点更新路径长度,即从 ii 号顶点到 jj 号顶点只经过前 kk 号点的最短路程。

而这里恰好加入了时间点,所以对于每一个时间点,不就是借助时间点内的点作为中转吗?

所以我们采用 Floyd,依次按照时间,把之前的中转点更新即可。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m,q,t[211];
int dis[210][210];
void floyd(int k)
{
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			if(dis[i][j]>dis[i][k]+dis[k][j])
				dis[i][j]=dis[i][k]+dis[k][j];
	return ;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++)
		scanf("%d",&t[i]);
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			dis[i][j]=1e9;
	for(int i=0;i<n;i++)
		dis[i][i]=0;
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		dis[a][b]=dis[b][a]=c;
	}
	scanf("%d",&q);
	int now=0;
	for(int i=1;i<=q;i++)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		while(t[now]<=c&&now<n)//更新中转点
		{
			floyd(now);
			now++;
		}
		if(t[a]>c||t[b]>c)//判断时间是否冲突
		{
			printf("-1\n");
			continue;
		}
		if(dis[a][b]==1e9)
			printf("-1\n");
		else
			printf("%d\n",dis[a][b]);
	}
	return 0;
}