P1119 灾后重建


题解

我觉得这个题出的很好,让我对 Floyd 算法有了一个更深的理解。

浅谈 Floyd

Floyd 是一个求多源最短路径的算法,算法的内容很简单。
这个算法的主要思路,就是通过其他的点进行中转来求的两点之间的最短路。因为我们知道,两点之间有多条路,如果换一条路可以缩短距离的话,就更新最短距离。而它最本质的思想,就是用其他的点进行中转,从而达到求出最短路的目的。
\(f[i][j][k]\) 表示从 \(i\)\(j\) 只经过编号为 \(1\)~\(k\) 的节点的最短路。
转移的话需要考虑两种情况:最短路经过 \(k\) 和最短路不经过 \(k\),那么就可以写出转移方程:
\(f[i][j][k]=\min(f[i][j][k-1],f[i][k][k-1]+f[k][j][k-1])\)
但是三维的状态我们无法接受,需要考虑优化。
由于 \(k\) 是由 \(k-1\) 转移来的,所以我们可以在外层枚举 \(k\),这样就可以省掉第三维的状态。
核心 \(Code:\)

for(int k=1;k<=n;k++)         //在最外层枚举中转点k 
    for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	    if(i!=k&&i!=j)
	        f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

虽然 Floyd 算法的代码很简单,但是它的本质思想还是很重要的,而此题恰恰巧妙地考查了这点。

简化题面

再回来看这个题,这个题就是让我们求多次询问的最短路,每次询问都会有一些点无法经过。

思路

如果我们对于每次询问都跑一次 Floyd 算法的话时间复杂度肯定是爆炸的,这就提醒我们可以离线操作。
注意到对于每次询问给出的时间 \(T\)我们需要在 \(t[i]<=T\) 的所有点中跑最短路。换句话说,我们需要求 \(f[i][j][T]\) 表示从 \(i\)\(j\) 只经过 \(t<=T\) 的点的最短路。
发现这和 Floyd 算法的本质思想一致,那么我们就可以顺水推舟地往下做了:
我们按照每个点的时间 \(t\) 来从小到大枚举 \(k\),这样每次内层循环结束后我们就会更新所有 \(t<=t[k]\) 的点之间的最短路。
然后我们每处理完一个中转点 \(k\) 之后就看看能否回答一些询问,能回答就输出。由于题目中保证 \(T\) 是递增的,所以我们只要读入+存储就好了,不必再按照时间排序了。

细节

此题我们用邻接矩阵来存图,一定要处理好初始化的问题。

\(Code\)

#include<algorithm>
#include<iostream>
#include<cstdio>
using namespace std;
const int N=205;
const int INF=1e9;
int read()                              //读入优化      
{
    int a=0,x=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
	if(ch=='-') x=-x;
	ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
	a=(a<<1)+(a<<3)+(ch^48);
	ch=getchar();
    }
    return a*x;
}
int n,m,top=1;
int t[N],id[N],f[N][N];
struct node                              //记录每个询问的信息 
{
    int u,v,t;
}a[1000000];
bool cmp(int x,int y)                    //按照t从小到大排序 
{ 
    return t[x]<t[y];
}
int main()
{
    n=read();m=read();
    for(int i=0;i<n;i++)                 //注意是从0开始编号 
    {
	t[i]=read();                     //t[i]表示第i个点修成的时间 
	id[i]=i;                         //id[i]表示第i个点的编号为i 
    }
    for(int i=0;i<n;i++) 
	for(int j=0;j<n;j++)
	    if(i!=j) f[i][j]=INF;        //边初始化INF 
    for(int i=1;i<=m;i++)         
    {
	int u=read();
	int v=read();
	int w=read();
	f[u][v]=f[v][u]=w;               //注意双向建边 
    }
    int q=read(); 
    for(int i=1;i<=q;i++)                //我们将q次询问存起来离线处理 
    {
	a[i].u=read();
	a[i].v=read();
	a[i].t=read();
    }
    sort(id,id+n,cmp);                   //将每个点按照t从小到大排序,排完序后id[i]表示t从小到大排第i的数的编号 
    for(int k=0;k<n;k++)                 //最外层循环枚举k,求t<=t[id[k]]的所有点间的最短路 
    {
        while(a[top].t<t[id[k]])         //如果能回答一些询问(询问时间a[i].t内的最短路已经求过了) 
	{
	    int u=a[top].u;
	    int v=a[top].v;
	    if(f[u][v]>=INF||t[u]>a[top].t||t[v]>a[top].t) printf("-1\n");   //注意无解情况 
	    else printf("%d\n",f[u][v]);
	    top++;                       //下一个问题 
	}
	for(int i=0;i<n;i++)             //松弛操作,Floyd算法核心 
	    for(int j=0;j<n;j++)
	    	f[id[i]][id[j]]=min(f[id[i]][id[j]],f[id[i]][id[k]]+f[id[k]][id[j]]);
    }
    while(top<=q)                        //所有点都建好了,但是询问还没问完,接着把剩下的输出,且此时不用考虑时间的影响 
    {
    	int u=a[top].u;
	int v=a[top].v;
	if(f[u][v]>=INF) printf("-1\n"); //无解的情况 
	else printf("%d\n",f[u][v]);
	top++;
    }
    return 0;
}
posted @ 2020-08-07 16:19  暗い之殇  阅读(95)  评论(0编辑  收藏  举报