最短路相关模型

本文介绍了如何利用图论中的Dijkstra算法和Bellman-Ford算法解决最短路问题,包括严格次短路的计算以及在有差分约束条件下的最短路求解。通过实例解析了P4878[USACO05DEC]LayoutG题目,展示了如何将实际问题转化为图论模型,并用代码实现。同时,强调了在处理负权边时使用Bellman-Ford算法的必要性。
摘要由CSDN通过智能技术生成

严格次短路

简洁明了,就是求图 G G G s s s t t t的次短路,不过它在长度上要严格大于最短路。

我们维护两个数组, d i s t i dist_i disti表示 s s s i i i的最短距离, d i s t i ′ dist'_i disti表示 s s s i i i的严格次短距离,在图 G G G上跑一遍 s s s t t t D i j k s g t r a Dijksgtra Dijksgtra,每次更新距离时优先更新 d i s t i dist_i disti,其次再更新 d i s t i ′ dist'_i disti

其实次短距离(不一定比最短距离长)做法十分相似,稍微改一下就行,详见代码。

代码
int n, m;
LL dist[N], dist2[N];
vector<Edge> e[N];

void Dijkstra(){
    rep(i, n + 1) dist[i] = dist2[i] = inf;
    priority_queue<pair<LL, int> > pq;
    dist[1] = 0;
    pq.push(MP(-dist[1], 1));
    while (!pq.empty()){
        int cur = pq.top().second;
        int curdis = -pq.top().first;
        pq.pop();
        if (curdis > dist2[cur]) continue;
        for (Edge u : e[cur]){
            if (curdis + u.len < dist[u.to]){
                dist2[u.to] = dist[u.to];
                dist[u.to] = curdis + u.len;
                pq.push(MP(-dist[u.to], u.to));
            } else if (curdis + u.len < dist2[u.to] && curdis + u.len > dist[u.to]){
                dist2[u.to] = curdis + u.len; // curdis + u.len > dist[u.to]表示严格次短
                pq.push(MP(-dist2[u.to], u.to));
            }
        }
    }
}

最短路中的差分约束

在有向图 G G G中,我们记 s s s i i i的最短距离为 d i d_i di。若存在一条从 u u u v v v权值为 w w w的边,则有 d u + w ≥ d v d_u + w \ge d_v du+wdv成立。

在满足所有这些不等式的 d d d中, d t − d s d_t - d_s dtds最大值就是 s s s t t t最短距离

考虑粗略地证明一下。首先,若 d i d_i di就是最短距离,则一定满足 d u + w ≥ d v d_u + w \ge d_v du+wdv,转化形式如下。
d v ≤ min ⁡ e = ( u , v ) ∈ E { d u + w } d_v \le \min_{e = (u, v) \in E}\{d_u + w\} dve=(u,v)Emin{du+w}
由于求最短路时 d d d都初始化为 inf ⁡ \inf inf,如果一些 d u d_u du未在 d v d_v dv之前求出,由于约束是取 m i n min min,所以对 d v d_v dv的最大值是没有影响的。因此我们可以按一定的顺序来逐个求解 d i d_i di的最大值。

s s s i i i的最短距离不是 d i − d s d_i - d_s dids的最大值呢?那么 ∀ e = ( u , v ) ∈ E , d u + w > d v \forall e = (u, v) \in E, d_u + w \gt d_v e=(u,v)E,du+w>dv,也就不存在路径了。

来看一道很好的例题。

P4878 [USACO05DEC]Layout G
题目描述

F a r m e r   J o h n Farmer~John Farmer John的农场上,有 N N N头奶牛在排队等候喂食,编号为 1 , 2 , ⋯   , N 1, 2, \cdots, N 1,2,,N,编号小的奶牛一定排在编号大的奶牛之前。

M L M_L ML对奶牛希望彼此之间不要超过一定的距离,还有另外 M D M_D MD对奶牛则希望相互之间不要小于一定的距离。

F a r m e r   J o h n Farmer~John Farmer John想请你来求出 1 1 1号奶牛和 N N N号奶牛之间的最大距离。

输入格式

第一行三个整数 N , M L , M D N, M_L, M_D N,ML,MD

接下来 M L M_L ML行,每行三个整数 A , B , D A, B, D A,B,D,分别表示 A A A号奶牛和 B B B号奶牛之间的最大距离。

再接下来 M D M_D MD行,每行三个整数 A , B , D A, B, D A,B,D,分别表示 A A A号奶牛和 B B B号奶牛之间的最小距离。

输出格式

若无法排成一队,输出 − 1 -1 1;若 1 1 1号奶牛和 N N N号奶牛之间的距离可以无穷大,输出 − 2 -2 2;否则输出最大可能距离。

数据范围与提示

2 ≤ N ≤ 1000 2 \le N \le 1000 2N1000

1 ≤ M L , M D ≤ 1 0 4 1 \le M_L,M_D \le 10 ^ 4 1ML,MD104

1 ≤ A , B ≤ N 1 \le A, B \le N 1A,BN

1 ≤ D ≤ 1 0 6 1 \le D \le 10 ^ 6 1D106

题解

1 1 1号奶牛到 i i i号奶牛的最大距离为 d i d_i di,我们可以上述约束转化为三类不等式。

  1. 编号小的排在编号大的前,等价于 d i < d i + 1 → d i + 1 ≥ d i d_i \lt d_{i + 1} \rightarrow d_{i + 1} \ge d_i di<di+1di+1di(后面会解释为什么可以与等号)。
  2. M L M_L ML对约束, d A + D ≥ d B d_A + D \ge d_B dA+DdB
  3. M D M_D MD对约束, d A + D ≤ d B → d B − D ≥ d A d_A + D \le d_B \rightarrow d_B - D \ge d_A dA+DdBdBDdA

于是建图。

  1. i + 1 i + 1 i+1 i i i连一条权值为 0 0 0的边。
  2. A A A B B B连一条权值为 D D D的边。
  3. B B B A A A连一条权值为 − D -D D的边。

1 1 1 N N N跑一边 B e l l m a n _ F o r d Bellman\_Ford Bellman_Ford就行了,因为图中有负权边,所以不能跑 D i j k s t r a Dijkstra Dijkstra

关于 − 1 -1 1 − 2 -2 2的判断问题。 − 1 -1 1代表图中存在负环, − 2 -2 2代表原图不连通。

来谈一谈“从 i + 1 i + 1 i+1 i i i连一条权值为 0 0 0的边”的精妙之处。

首先如果我们对 i i i有了约束,那么对 1 ∼ i − 1 1\sim i- 1 1i1就都有了约束,所以这并不影响图的连通性的判定。

另外,由于 D D D的取值范围缘故,只要 A A A号奶牛和 B B B号奶牛之间有距离, i ( A < i < B ) i(A\lt i \lt B) i(A<i<B)号奶牛就可以找到自己的位置,所以对答案的贡献为 0 0 0

代码
#include <bits/stdc++.h>
#define PB push_back
#define MP make_pair
using namespace std;

#define rep(i, n) for (int i = 0; i < (int)(n); ++ i)
#define rep1(i, n) for (int i = 1; i < (int)(n); ++ i)
#define foreach(itr, c) for (__typeof((c).begin()) itr = (c).begin(); itr != (c).end(); ++ itr)

typedef long long LL;
typedef pair<int, int> pii;

const int N = 1005;
const LL inf = 1e16 + 7;

int n, ml, md;
LL dist[N];

struct Edge {
    int to;
    LL len;
};
vector<Edge> e[N];

LL Bellman_Ford(){
    rep(i, n + 1) dist[i] = inf;
    dist[1] = 0;
    rep(cnt, n){
        bool update = false;
        rep1(i, n + 1) for (Edge u : e[i]) if (dist[i] + u.len < dist[u.to]){
            dist[u.to] = dist[i] + u.len;
            update = true;
            if (cnt == n - 1) return -1;
        }
        if (!update) break;
    }
    if (dist[n] == inf) return -2;
    return dist[n];
}

int main(){
    scanf("%d%d%d", &n, &ml, &md);
    rep(i, ml){
        int u, v; LL w;
        scanf("%d%d%lld", &u, &v, &w);   // d_u + w >= d_v
        e[u].PB(Edge{v, w});
    }
    rep(i, md){
        int u, v; LL w;
        scanf("%d%d%lld", &u, &v, &w);   // d_u + w <= d_v  -->  d_v - w >= d_u
        e[v].PB(Edge{u, -w});
    }
    rep1(i, n) e[i + 1].PB(Edge{i, 0});
    LL ret = Bellman_Ford();
    printf("%lld\n", ret);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值