最短路
最短路
下文的 1
设为起点。
Dijkstra
是个单源最短路。
思路
把状态设计为 (当前这个点,长度)
,那么只有当当前长度是从 1
开始走到当前点的最短路,才能做转移,于是可以记录任何一个点到 1
的最小距离,不断更新这个最短距离,然后对于这条最短路开始转移即可,由于是用当前点的最短路,所以得用堆。由于是一个贪心的选边,所以遇到负权值就不灵了,应为当前点不一定和堆头最靠近。
代码
选自 P4779 单源最短路径
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int MaxN = 1e5 + 10;
struct S {
int v, w;
bool operator<(const S &j) const {
return w > j.w;
}
};
int n, m, s;
int d[MaxN];
bool vis[MaxN];
vector<S> g[MaxN];
priority_queue<S> q;
void Record(int u, int v, int w) {
if (d[v] <= d[u] + w) {
return;
}
d[v] = d[u] + w;
q.push({v, d[v]});
}
int main() {
cin >> n >> m >> s;
for (int i = 1, u, v, w; i <= m; i++) {
cin >> u >> v >> w;
g[u].push_back({v, w});
}
fill(d + 1, d + n + 1, 1e9);
for (Record(0, s, 0); !q.empty();) {
int u = q.top().v; // 用最优的来更新其它点
q.pop();
if (vis[u]) { // 得在下面标记,不然不是最优解,只有开始扩展了的才能说是已经最优的了
continue;
}
vis[u] = 1;
for (S i : g[u]) {
Record(u, i.v, i.w);
}
}
for (int i = 1; i <= n; i++) {
cout << d[i] << " ";
}
return 0;
}
时间复杂度
\(O(mlogn)\)
空间复杂度
\(O(n + m)\)
Spfa
Spfa它死了是一个可以处理负权值的时间复杂度极不稳定的算法。
思路
感觉和 Dijkstra 没什么区别,但是它并不是用最短路去更新,是不断地用队头去更新别的点,如果更短就更新,不过由于当前点的最短路不一定就是队头的方案,所以要将自己标记为未走过,而被松弛的点要标记为走过
代码
选自 P3385 负环
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
using ll = long long;
const int MaxN = 2e3 + 10;
struct Edge {
int to, nxt, w;
} e[MaxN << 2];
struct S {
ll x, w;
};
ll d[MaxN], t, n, m;
int head[MaxN], cnt[MaxN], tot;
bool vis[MaxN], flag;
queue<S> q;
bool Record(int u, int v, int w) {
if (d[v] <= d[u] + w) {
return 0;
}
d[v] = d[u] + w;
if (vis[v]) {
return 0;
}
vis[v] = 1;
q.push({v, d[v]});
return ++cnt[v] > n;
}
bool spfa() {
fill(d + 1, d + n + 1, 1e10);
fill(vis + 1, vis + n + 1, 0);
fill(cnt + 1, cnt + n + 1, 0);
queue<S>().swap(q);
for (flag = 0, Record(0, 1, 0); !q.empty() && !flag;) {
S u = q.front();
q.pop();
vis[u.x] = 0;
for (int i = head[u.x]; ~i; i = e[i].nxt) { // caoshurui 大巨佬教我的方法:~i
Record(u.x, e[i].to, e[i].w) && (flag = 1);
}
}
return flag;
}
int main() {
for (cin >> t; t; t--) {
cin >> n >> m;
fill(head + 1, head + n + 1, -1);
tot = 0;
for (int i = 1, u, v, w; i <= m; i++) {
cin >> u >> v >> w;
e[++tot] = {v, head[u], w};
head[u] = tot;
if (w >= 0) {
e[++tot] = {u, head[v], w};
head[v] = tot;
}
}
cout << (spfa() ? "YES" : "NO") << endl;
}
return 0;
}
时间复杂度
最坏:\(O(n^2)\)
平均:\(O(mlogn)\)
空间复杂度
\(O(n+m)\)
势能 Dijkstra
弥补了普通 Dijkstra 的缺点的快速算法,即可以运行负权值的 Dijkstra。
思路
其实就是把负权值用一种方法变成正的。下列都用 \(\color{black}ABC237E\ \ Skiing\) 的思路来讲。
原本的边权是 \(w_{uv} = h_u - h_v\) 或 \(w_{uv}=(h_u - h_v) \times 2\),现在加上一个势能变为:\(w_{uv}+h_u-h_v\),这个势能可以理解为减掉一部分后又加上了更多的一部分,那么那段减掉的就被抵消了,从而从负边权变成了正的,那么从点 1 到点 n 的权值从:\(w_{1v_1}+w_{v_1v_2}+ \dots + w_{v_xv_n}\)变成了:\((w_{1v_1}+h_1-h_{v_1})+(w_{v_1v_2}+h_{v_1}-h_{v_2})+ \dots + (w_{v_xv_n}+h_{v_x} - h_n)=w_{1v_1}+w_{v_1v_2}+ \dots + w_{v_xv_n}+h_1-h_n\)
由于本题是求最长路,可以将它取反求最短路。
那么边权就可以推出来了:
-
如果 \(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_u-h_v)\),取反为 \(2(h_v - h_u)\),加上势能为:\(2h_v-2h_u+h_u-h_v=h_v-h_u > 0\)
边权都为正,可以跑 Dijkstra。
势能一般可以用 Spfa 从 0 点跑到其他点的最短路,求出来,虽然说感觉有点鸡肋,但在\(\color{black}这道题\)中只能这么做
代码
选自 ABC237E Skiing
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int MaxN = 2e5 + 10;
struct Node {
int v;
long long d;
bool operator<(const Node &j) const {
return d > j.d;
}
};
int n, m;
vector<int> g[MaxN];
long long d[MaxN], h[MaxN], ans;
priority_queue<Node> q;
bool vis[MaxN];
void Record(int u, int v, long long w) {
if (d[v] <= d[u] + w) {
return;
}
d[v] = d[u] + w;
q.push({v, d[v]});
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> h[i];
d[i] = 1e18;
}
for (int i = 1, u, v; i <= m; i++) {
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (Record(0, 1, 0); !q.empty();) {
Node u = q.top();
q.pop();
if (vis[u.v]) {
continue;
}
vis[u.v] = 1;
for (int i : g[u.v]) {
if (h[u.v] > h[i]) {
Record(u.v, i, 0);
} else {
Record(u.v, i, h[i] - h[u.v]);
}
}
}
for (int i = 1; i <= n; i++) {
(d[i] != -1e18) && (ans = max(ans, -(d[i] - (h[1] - h[i]))));
}
cout << ans << endl;
return 0;
}
时间复杂度
\(O(mlogn)\)
空间复杂度
\(O(n + m)\)