Skiing(势能Dijkstra)

题意

给定\(n\)个点,\(m\)条边的有向连通图,每个点\(i\)有点权\(h_i\)。对于每条边\((u, v)\),如果\(h_u > h_v\),边权为\(h_u - h_v\);如果\(h_u < h_v\),边权为\(-2(h_v - h_u)\);如果\(h_u = h_v\),边权为0。求从\(1\)号点出发的最长路。

数据范围

\(2 \leq n \leq 200000\)
\(n - 1 \leq m \leq 200000\)

思路

放一篇讲解出色的题解:https://zhuanlan.zhihu.com/p/470948347

首先,做最长路,可以将所有边的边权取反,然后做最短路。于是问题就转化为了一个带有负权的最短路问题。

朴素的Dijkstra算法是无法处理带有负权的最短路问题的,而SPFA算法时间复杂度为\(O(n^2)\)。这里引入一个技巧:势能Dijkstra。

我们先来探讨一个处理方法,就是将每条边的边权同时加上一个常数\(c\),使得每条边的边权都是非负实数。但是这个方法其实是不正确的,因为路径的边数不定!假设之前的最短路长度为\(s_1\),边数为\(n_1\);另一条路径的长度为\(s_2\),边数为\(n_2\),且\(s_2 > s_1\)\(n_2 < n_1\)。处理过后的最短路长度为\(s_1 + n_1c\),另外那条路径长度为\(s_2 + n_2c\),这两者大小关系不定,原来的最短路不一定是现在的最短路,因此此方法不成立。

势能Dijkstra也是为每条边加上一个数,使得所有边的边权都为非负实数,但是区别在于加上的这个数是不定的。具体做法如下,为每个点引入一个点权\(h_i\),对于每条边\((u, v)\),假设边权为\(w_{uv}\),处理后的边权为\(w_{uv} + h_u - h_v\)
那么对于一条路径\(u-v_1-v_2-\cdots-v_n\),最短路为\((w_{uv_1} + h_u - h_{v_1}) + (w_{v_1v_2} + h_{v_1} - h_{v_2}) + \dots + (w_{v_{n - 1},v_n} + h_{v_{n - 1}} - h_{v_n}) = w_{uv_1} + w_{v_1v_2} + \dots + w_{v_{n - 1},v_n} + h_{1} - h_{v_n}\)
因此路径长度的改变量只与首尾的点权有关,这保证了算法的正确性。

在本题中已经提供了点权。我们只需要分析一下边权,对于边\((u, v)\)

  • \(h_u > h_v\)
    边权为\(h_u - h_v\),取反后为\(h_v - h_u\),加上势能后为\(h_v - h_u + (h_u - h_v) = 0\)
  • \(h_u < h_v\)
    边权为\(-2(h_v - h_u)\),取反后为\(2(h_v - h_u)\),加上势能后为\(2(h_v - h_u) + (h_u - h_v) = h_v - h_u > 0\)

接下来跑一遍Dijkstra算法,最终答案为\(\max_{1 \leq i \leq n}\{-(d_i - (h_1 - h_i))\}\),其中\(d_i\)\(1\)\(i\)的最短距离。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

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

const int N = 200010, M = 2 * N;

int n, m;
ll v[N];
int h[N], e[M], w[M], ne[M], idx;
ll d[N];
bool st[N];

void add(int a, int b, ll c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}

void dijkstra()
{
    priority_queue<pii, vector<pii>, greater<pii> > que;
    for(int i = 1; i <= n; i ++) d[i] = 1e18;
    que.push({0, 1});
    d[1] = 0;
    while(que.size()) {
        auto t = que.top();
        que.pop();
        int ver = t.second;
        ll distance = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        for(int i = h[ver]; ~i; i = ne[i]) {
            int j = e[i];
            if(d[j] > distance + w[i]) {
                d[j] = distance + w[i];
                que.push({d[j], j});
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for(int i = 1; i <= n; i ++) scanf("%lld", &v[i]);
    for(int i = 0; i < m; i ++) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b, max(0ll, v[b] - v[a]));
        add(b, a, max(0ll, v[a] - v[b]));
    }
    dijkstra();
    ll ans = 0;
    for(int i = 1; i <= n; i ++) {
        if(d[i] == -1e18) continue;
        ans = max(ans, -(d[i] - v[1] + v[i]));
    }
    printf("%lld\n", ans);
    return 0;
}
posted @ 2022-03-15 11:04  pbc的成长之路  阅读(504)  评论(0编辑  收藏  举报