题意
给定\(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;
}