洛谷 P1119 灾后重建

d4f2d97d11bb48459147025d27392c17.png

8780c946a077410fb2b4947caa6aaa73.png

70e66a62636b438cab79e3d546c52ff1.png

df5bef53fc154af78d58cb6b4fea7684.png


此题难点在于询问的次数足足有50k次,任何一个最短路算法去跑个50k次原图基本上都是没戏了。 

在最短路的题目中,如果节点数量非常的少,那就是在提示我们使用Floyd了。

inline void floyd(){
    for(int k=1;k<=n;++k)
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                if(i!=j)d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}

大部分人只知道使用floyd
却不知道floyd为何正确

根据floyd的式子我们可以得出其实就是这个递推式子

9c6ae623f1ed4ebf903a5fe309751b26.png

如果当k=1的时候发生了中转,但是我的最小值其实只需要在3和4的时候中转,那这个式子是不是错了呢?

若在1处发生中转,中转之后只有两种情况:1.比3更短   2.比3更长 不考虑相等的情况

1.如果更短,和原答案不符

2.如果更长,肯定会被更新成从3中转

证毕

故不会出现上述情况。

扩展至三维数组用定义也可简单证明

设总结点n
定义f[k][i][j]为i->j 只能通过 前k个节点的最短路径

根据定义可以知道k=n时为i->j在整个图中的最短路径
根据定义可以推出以下式子
1.经过k节点,最短路不能被更新.f[k][i][j]=f[k-1][i][j]
2.经过k节点,最短路能被更新.f[k][i][j]=f[k-1][i][k]+f[k-1][k][j]

综合1.2.即f[k][i][j]=min(f[k-1][i][j],f[k-1][i][k]+f[k-1][k][j])

由此可见k可以直接拿到外面去循环

再看题目所给的时间 t 都十分巧妙的保持了升序。a067ebcad3a44dda88d050127c90dc57.png

ad6074abb78c4b168253383f0b04ce70.png 所以只要根据原三维dp思想进行一遍floyd就可以做出来了,因为城市可中转范围是从1~n有序的,而不是乱序的。

假设第一次询问第三天,到第三天为止已经有1~4城市能中转了,那么外部循环只要k=1~k=4范围都跑一次就可以得到答案。

因为时间是降不下来的,所以k是逐渐扩大的,那么微调一下k就可以解决这道题。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int MAX=INT_MAX>>1;

int n,m,t[200],Q,u,v,w,x,y,qt,now;
int e[200][200];
inline void inite(){
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j)
            e[i][j]=MAX;
    for(int i=0;i<n;++i)
        e[i][i]=0;
}
inline void update(int k){//核心1号
    for(int i=0;i<n;++i){
        for(int j=0;j<n;++j){
            e[i][j]=e[i][k]+e[k][j]<e[i][j]?e[i][k]+e[k][j]:e[i][j];
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;++i){
        scanf("%d",&t[i]);
    }
    inite();
    for(int i=0;i<m;++i){
        scanf("%d%d%d",&u,&v,&w);
        e[u][v]=e[v][u]=w;
    }
    scanf("%d",&Q);
    for(int i=0;i<Q;++i){
        scanf("%d%d%d",&x,&y,&qt);
        while(t[now]<=qt&&now<n){//核心2号
            update(now);
            now++;
        }
        if(t[x]>qt||t[y]>qt||e[x][y]==MAX)cout<<-1<<endl;
        else cout<<e[x][y]<<endl;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值