【图论】Johnson算法
适用于求解没有负环的全源最短路,最坏时间复杂度 \(O(nm\log m)\) 比Floyd要优秀(但是Floyd可以找出负环)。
在没有负权边时,使用n次单源最短路Dijkstra代替即可。
算法流程:
1、新建一个虚拟节点(编号为n+1),向[1,n]连接一条边权为0的虚拟边。
2、从n+1号节点开始跑一次队列优化BellmanFord,得到从n+1号虚拟节点到节点i的最短路hgt[i],假如存在负环,则无法求解。
3、删除虚拟节点和虚拟边(也可以直接选择忽视他们)
4、把每条非虚拟边(u,v,w)改为(u,v,w+hgt[u]-hgt[v])
5、对于每个点i属于[1,n],跑一次Dijkstra,得到从i节点到[1,n]的非虚拟节点的最短路dis[j]。
6、原本的[i,j]的最短路为dis[j]+hgt[i]-hgt[j]
最后把边权修改回来的操作可以省去。
namespace Johnson {
const int MAXN = 2e5 + 10;
int n;
vector<pil> G[MAXN];
ll hgt[MAXN];
ll dis[MAXN];
int cnt[MAXN];
bool vis[MAXN];
queue<int> Q;
priority_queue<pli> PQ;
bool bellmanford() {
fill(hgt + 1, hgt + 1 + n, LINF);
fill(cnt + 1, cnt + 1 + n, 0);
fill(vis + 1, vis + 1 + n, 0);
while(!Q.empty())
Q.pop();
hgt[n] = 0;
vis[n] = 1;
Q.push(n);
while(!Q.empty()) {
int u = Q.front();
Q.pop();
vis[u] = 0;
++cnt[u];
if(cnt[u] == n)
return false;
for(pil &p : G[u]) {
int v = p.first;
ll w = p.second;
if(hgt[v] <= hgt[u] + w)
continue;
hgt[v] = hgt[u] + w;
if(!vis[v]) {
vis[v] = 1;
Q.push(v);
}
}
}
return true;
}
void dijkstra(int s) {
fill(dis + 1, dis + 1 + n, LINF);
fill(vis + 1, vis + 1 + n, 0);
while(!PQ.empty())
PQ.pop();
dis[s] = 0;
PQ.push({-dis[s], s});
while(!PQ.empty()) {
int u = PQ.top().second;
PQ.pop();
if(vis[u])
continue;
vis[u] = 1;
for(pil &p : G[u]) {
int v = p.first;
ll w = p.second;
if(vis[v] || dis[v] <= dis[u] + w)
continue;
dis[v] = dis[u] + w;
PQ.push({-dis[v], v});
}
}
}
bool johnson() {
++n;
G[n].clear();
for(int i = 1; i <= n - 1; ++i)
G[n].eb(i, 0);
int res = bellmanford();
G[n].clear();
--n;
if(!res)
return false;
for(int u = 1; u <= n; ++u) {
for(pil &p : G[u]) {
int v = p.first;
p.second += hgt[u] - hgt[v];
}
}
for(int i = 1; i <= n; ++i) {
dijkstra(i);
for(int j = 1; j <= n; ++j) {
if(dis[j] != LINF)
dis[j] -= hgt[i] - hgt[j];
}
// do something
}
for(int u = 1; u <= n; ++u) {
for(pil &p : G[u]) {
int v = p.first;
p.second -= hgt[u] - hgt[v];
}
}
return true;
}
}