一道有趣的最短路 NEERC2017 Journey from Petersburg to Moscow

本文介绍了一种求解特定最短路径问题的方法,即计算从起点到终点的最短路径,但仅考虑路径中最大的k条边的权重。通过调整边权并使用Dijkstra算法,可以有效地找到最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

http://codeforces.com/gym/101630/attachments/download/6401/20172018-acmicpc-northeastern-european-regional-contest-neerc-17-en.pdf

题意

1 1 n的最短路,最短路上只计算前 k k 大的边。

题解

这道题的操作很骚,算法如下:
遍历每条边x,并把图中所有的边的权值都减去该边的权值 x x ,如果变成负数,那么就置0,并将跑出来的值 dis[n]+kx d i s [ n ] + k ∗ x 就是这次的答案,对所有的答案取最小值,并且与原始图的 dis[n] d i s [ n ] 取最小值,得到的结果就是最终答案。

正确性证明:
1. 假设最终的最短路中有大于等于 k k 条边,并设第k大的边长度为 x x
那么该路径上所有的边减去x,之后跑最短路得到 dis[n] d i s [ n ] ,那么 dis[n]+kx d i s [ n ] + k ∗ x 就是该路径的“长度”,就是最终答案,并且这个数一定会出现在比较中。
而对于其他的路径,如果减去的 x x 不是该路径的第k大的边的时候,该路径的值 dis[n]+kx d i s [ n ] + k ∗ x 一定不会比该路径的“长度”小,出现在比较中不会对最终答案造成影响。
2. 假设最短路中有小于k条边,那么原图中跑最短路的 dis[n] d i s [ n ] 一定是最小的。

注意,此题用spfa超时,用dijkstra也有稍微优化一下才行。

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;
const int maxn = 3007;
vector<int> G[maxn];
typedef long long ll;
typedef pair<ll,int> pii;
const ll inf = 1e18;
int U[maxn],V[maxn];long long W[maxn];
int n,m,k;
int vis[maxn];ll dis[maxn];
#define pr(x) cout<<#x<<":"<<x<<endl
/*
ll spfa(ll x){
    memset(vis,0,sizeof(vis));
    for(int i = 1;i <= n;++i) dis[i] = inf;
    queue<int> Q;
    vis[1] = 1;
    Q.push(1);
    dis[1] = 0;
    while(!Q.empty()){
        int u = Q.front();Q.pop();
        vis[u] = 0;
        for(auto e : G[u]){
            int v = U[e]^V[e]^u;
            ll w = W[e] - x;
            w = max(0ll,w);
            if(dis[v] > dis[u] + w){
                if(!vis[v]) Q.push(v);
                dis[v] = dis[u] + w;
                vis[v] = 1;
            }
        }
    }
    return dis[n];
}
*/
ll dij(ll x){
    priority_queue<pii,vector<pii>,greater<pii> > Q;
    for(int i = 1;i <= n;++i) dis[i] = inf;
    dis[1] = 0;
    Q.push({0,1});
    while(!Q.empty()){
        pii p = Q.top();Q.pop();
        int u = p.second;
        if(p.first > dis[u]) continue;
        for(int e : G[u]){
            int v = u ^ U[e] ^ V[e];
            ll w = W[e] - x;
            w = max(0ll,w);
            if(dis[v] > dis[u] + w){
                dis[v] = dis[u] + w;
                Q.push({dis[v],v});
            }
        }
    }
    return dis[n];
}
int main(){
    cin>>n>>m>>k;
    for(int i = 0;i < m;++i){
        int u,v;long long w;
        scanf("%d %d %lld",&u,&v,&w);
        U[i] = u,V[i] = v,W[i] = w;
        G[u].push_back(i);
        G[v].push_back(i);
    }
    ll ans = dij(0);
    for(int i = 0;i < m;++i){
        ll res = dij(W[i])+k*W[i];
        ans = min(ans,res);
    }
    printf("%lld\n",ans);
    return 0;   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值