• 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;
    }
    
  • 相关阅读:
    Android反射打造万能SharedPreferences
    Android 通过反射让SQlite建表
    Android--Listview优化
    Android Touch事件传递机制
    [转]Android 完美的隐藏软键盘方法 点击编辑框外软键盘消失
    Android之异步线程原理
    Android应用性能优化之使用SparseArray替代HashMap
    在xocde运行profile 遇到"Existing default base temp directory '/Library/Caches/com.apple.dt.instruments' has insufficient privileges for user id 505. Please have the owner delete this directory"
    UITableView——点击某一行移动到指定位置
    CocoaPods看的三篇文章
  • 原文地址:https://www.cnblogs.com/miraclepbc/p/16007565.html
Copyright © 2020-2023  润新知