洛谷 P1119 灾后重建

题目背景

B地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。

题目描述

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

输入格式:

输入文件rebuild.in的第一行包含两个正整数N,M,表示了村庄的数目与公路的数量。
第二行包含N个非负整数t[0], t[1], …, t[N – 1],表示了每个村庄重建完成的时间,数据保证了t[0] ≤ t[1] ≤ … ≤ t[N – 1]。
接下来M行,每行3个非负整数i, j, w,w为不超过10000的正整数,表示了有一条>连接村庄i与村庄j的道路,长度为w,保证i≠j,且对于任意一对村庄只会存在一条道路。
接下来一行也就是M+3行包含一个正整数Q,表示Q个询问。
接下来Q行,每行3个非负整数x, y, t,询问在第t天,从村庄x到村庄y的最短路径长度为多少,数据保证了t是不下降的。

输出格式:

输出文件rebuild.out包含Q行,对每一个询问(x, y, t)输出对应的答案,即在第t天,从村庄x到村庄y的最短路径长度为多少。如果在第t天无法找到从x村庄到y村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄y在第t天仍未修复完成,则输出-1。

这大概是我第一次独立A掉的提高+?

在这个特殊的日子里,我感觉我的生命在流逝 我来写博客纪念下。

不过这个题看起来挺水…

我太蒻了…

这道题求多源最短路,用floyd差不了了。

唉~不对…好像边会越来越多…

要不然加一次边跑一次spfa?

复杂度好像爆炸…

还是得用floyd…

那么只能一边加边一边跑了…

那么加一次边对那些点的最短路径有影响呢?

如果加的边val[u][v]比本来u和v的距离就大的话,好像没有什么影响…

因为要从u到v完全可以走之前的更短的路径而不是走直接相连的边…

如果比原来的距离大的话怎么办呢?

我们想想floyd算法,它是枚举每一个中间点,看看过这个点的路径能不能更新最短路…

也就是说,加了一条更短的边(u,v)以后,如果某两点间最短路径不经过边(u,v),那么它就不会受到影响。也就是说,只有经过点u和v的最短路径才可能受到影响。

那么我们就尝试用u和v作为中间点,尝试更新最短路!

问题好像迎刃而解了…

不存在的!

这样跑复杂度也会爆炸!

因为每一个刚刚建好的村庄可以和所有已经建好的村庄连线…而每个点都要求一遍最短路…

考虑优化,上面说到,只有经过并且经过v的最短路才会受到影响,所以我们可以用v作为中间点更新出u的到所有点的最短路,复杂度O(n),再用u作为中间点更新所有的最短路。

为什么是对的?

例如加边(u,v)后s–>t的最短路应更新为s–>…–>v–>u–>…–>t,如果用u作为中间点,我们就把最短路分成了两段,一段是s–>u,另一段是u–>t。u–t的最短路是不变的,上面的做法就是先更新s–>u的最短路(以v为中间点),这样就能求出s–>t的最短路了。

然后就过了!

(这个优化好像很弱)

代码如下:

#include<cstdio>
#include<iostream>
using namespace std;
const int INF=1e9;
int val[201][201];
int dis[201][201];
int ed[201][201]={0};
int t[201];
int ans[50001];
int n,m;
void dijkstra(int k)
{
    for(int i=0;i<n;i++)
    {
        if(ed[i][k]&&t[i]<=t[k]&&dis[i][k]>val[i][k])      //如果这条边已经建好了并且边长大于两点间本来的距离,就更新最短路
        {
            dis[i][k]=dis[k][i]=val[i][k];
            for(int j=0;j<n;j++)
            {
                if(dis[j][i]+dis[i][k]<dis[j][k])
                    dis[j][k]=dis[k][j]=dis[j][i]+dis[i][k];
            }
        }
    }
    for(int i=0;i<n;i++)                                   //以k为中间点的floyd
    {
        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];
        }
    }
}
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]=INF;
        }
    }
    for(int i=1;i<=m;i++)
    {
        int x,y,w;
        scanf("%d%d%d",&x,&y,&w);
        val[x][y]=val[y][x]=w;
        ed[x][y]=ed[y][x]=1;
    }

    int q,u,v,w,s,p=0;
    scanf("%d",&q);
    for(int l=1;l<=q;l++)
    {
        scanf("%d%d%d",&u,&v,&s);
        while(t[p]<=s&&p<n)                          //如果这个村庄在这一天已经修好了
        {
            dijkstra(p);
            p++;
        }
        if(dis[u][v]>1e8) ans[l]=-1;                 //这个特判有些玄学...
        else ans[l]=dis[u][v];
    }
    for(int i=1;i<=q;i++)
    {
        printf("%d\n",ans[i]);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值