前言
noip前刷模板时刷到了这个模板,然后随便看了一下题解发现还挺简单的就来把坑补了(虽然好像没什么应用,不补也没关系)
题意
给定一个包含 (n) 个结点和 (m) 条带权边的有向图,求所有点对间的最短路径长度,一条路径的长度定义为这条路径上所有边的权值和。
正文
Johnson最短路就是用来在稀疏图上解决上述问题的。
前置芝士:Floyd,Dijkstra,Bellman-Ford,SPFA
首先我们都学过一个多源最短路的算法:Floyd。它是基于dp的一种做法,详细的我就不说了自己随便上baidu搜一篇看看。它的时间复杂度是稳定的 (O(n^3)),其中 (n) 为点数,下同(看它三重循环也能看出来),这里给出代码实现:
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
由于其基于dp的本质所以对于什么样的图都是可做的,但这也有一个缺点:对于任何图复杂度都为 (O(n^3))。
考虑另外一个最短路算法:SPFA(这里就不说 Bellman_Ford 了,不过也默认SPFA的时间复杂度为 (O(nm)),其中 (m) 为边数,下同)。我们知道这是一个单源最短路算法,如果对于每个点作为起点跑一遍SPFA,时间复杂度为 (O(n^2m)),在完全图上被 Floyd 吊打,稀疏图(树)也只是和 Floyd 打平手,还不如就写 Floyd。但它有一个优点:可以跑负权图。这里给出代码实现:
queue<int> q;
q.push(s);
memset(dis, 0x3f, sizeof(dis));
vis[s] = true;
dis[s] = 0;
while (!q.empty()) {
int x = q.front();
q.pop();
for (int i = 0; i < (int)vec[x].size(); i++) {
#define y vec[x][i].to;
if (dis[y] > dis[x] + vec[x][i].val) {
dis[y] = dis[x] + vec[x][i].val;
if (vis[y] == false) {
vis[y] = true;
q.push(y);
}
}
#undef y
} vis[x] = false;
}
考虑另一个算法:Dijkstra,它是基于贪心的,如果采取堆优化时间复杂度可以到 (O(mlog{n}))。跑 (n) 次复杂度 (O(nmlog{n}))。在稀疏图上完爆上述两个做法。这里该出代码实现:
priority_queue<pair<int, int> > q;
q.push(make_pair(s, 0));
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
while (!q.empty()) {
int x = q.top();
q.pop();
if (vis[x] == true) continue;
vis[x] = true;
for (int i = 0; i <(int)vec[x].size(); i++) {
#define y vec[x][i].to
if (dis[y] > dis[x] + vec[x][i].val) {
dis[y] = dis[x] + vec[x][i].val;
q.push(make_pair(y, -dis[y]));
}
#undef y
}
}
但是这个算法有个缺点:不能处理负权图。为什么?因为他是基于贪心的一个算法,每次从堆顶取出来更新答案的 (x) 必须是已经确定答案的,而如果有负权的话有可能会得到更小的答案,所以就不对了。那么我们考虑如何将这个图变成非负权图。
想法1
首先有一个很直观的想法:全部加上某个值,可是这样答案是不正确的,原因是这样找到的最短路的值还和路径经过的边数有关,所以无法保证新图最短路和原图最短路相同。
想法2
考虑考虑最短路的性质:(u ightarrow v,dis[v]le dis[u]+val(u,v) Rightarrow val(u,v)+dis[u]-dis[v]ge 0)。这不就是一个非负数吗?那么我们考虑新建一个源点连向每个点一个长度为 (0) 的边,然后跑一遍 SPFA 求出到每个点的最短路 (dis[x]),然后对于每条边 (u ightarrow v),将其权值变为 (val(u,v)+dis[u]-dis[v]),再从每个点跑 Dijkstra 即可。时间复杂度 (O(nm+nmlog{n})) 稀疏图吊打 Floyd。
完整代码
#include <bits/stdc++.h>
using namespace std;
struct node{
int pre, to, val;
}edge[60005];
int head[30005], tot;
int n, m;
long long dis[30005], D[30005];
int cnt[30005];
bool vis[30005];
queue<int> q;
priority_queue<pair<long long, int> > pq;
void add_edge(int u, int v, int l) {
edge[++tot] = node{head[u], v, l};
head[u] = tot;
}
bool SPFA(int s) {
for (int i = 1; i <= n; i++) dis[i] = 0x3f3f3f3f3f3f3f3f;
q.push(s);
vis[s] = true;
dis[s] = 0;
cnt[s] = 1;
while (!q.empty()) {
int x = q.front();
q.pop();
for (int i = head[x]; i; i = edge[i].pre) {
int y = edge[i].to;
if (dis[y] > dis[x] + edge[i].val) {
dis[y] = dis[x] + edge[i].val;
cnt[y] = cnt[x] + 1;
if (cnt[y] > n + 1) return false;
if (vis[y] == false) {
vis[y] = true;
q.push(y);
}
}
} vis[x] = false;
}
return true;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add_edge(u, v, w);
}
for (int i = 1; i <= n; i++) add_edge(0, i, 0);
if (SPFA(0) == false) {
puts("-1");
return 0;
}
for (int i = 1; i <= n; i++) D[i] = dis[i];
for (int i = 1; i <= n; i++) {
for (int j = head[i]; j; j = edge[j].pre) {
int k = edge[j].to;
edge[j].val += dis[i] - dis[k];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) dis[j] = 100000000000, vis[j] = false;
dis[i] = 0;
while (!pq.empty()) pq.pop();
pq.push(make_pair(0, i));
while (!pq.empty()) {
int x = pq.top().second;
pq.pop();
if (vis[x] == true) continue;
vis[x] = true;
for (int k = head[x]; k; k = edge[k].pre) {
int y = edge[k].to;
if (dis[y] > dis[x] + edge[k].val) {
dis[y] = dis[x] + edge[k].val;
pq.push(make_pair(-dis[y], y));
}
}
}
long long ans = 0;
for (int j = 1; j <= n; j++) {
if (dis[j] >= 1000000000)
ans = ans + 1ll * j * 1000000000;
else
ans = ans + 1ll * j * (dis[j] - D[i] + D[j]);
}
printf("%lld
", ans);
}
return 0;
}