zoukankan      html  css  js  c++  java
  • 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
    ");   //注意无解情况 
    	    else printf("%d
    ",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
    "); //无解的情况 
    	else printf("%d
    ",f[u][v]);
    	top++;
        }
        return 0;
    }
    
  • 相关阅读:
    spring boot +mybatis 操作sqlite数据库
    katalon studio教程之通过录制/回放创建测试用例
    #katalon studio# 安装和设置(Installation and Setup)
    NET Core 基于Aspect Injector 实现面向AOP编程
    NET Core 3.1使用AutoMapper实现对象映射
    给NET core 智能感知提示安装中文汉化包
    代码注释规范
    软件升级版本号迭代规范-Semantic Versioning
    使用阿里云的Helm私有仓库
    Helm操作指南
  • 原文地址:https://www.cnblogs.com/xcg123/p/13453062.html
Copyright © 2011-2022 走看看