题外话:
因为滥用LaTeX被喷了,因此学姐把文风换了一换,也是为了大家更好食用嘛!
1,背景板
众所周知,Dijkstra 算法可以处理无负边权的图,Bellman—Ford 算法则可以处理有负边权的图。
因此,对于全源最短路,我们可以非常暴力地对每个点都跑一遍 Bellman—Ford。但是,这显然是不够快的,我们注意到堆优化的 Dijkstra 算法比 Bellman—Ford 更优秀。
于是,一个伟大的想法产生了,把 Dijkstra 的快速和 Bellman—Ford 的负边权适用性结合起来!(~ ̄▽ ̄)~
2,大致思想
这里直接贴上 Johnson 算法的思路,我们只需要了解流程,而正确性的证明是下一个子目要做的事。
1,我们先通过设置一个虚拟节点,将它和所有节点间都加一条边权为0的边,很明显,这样对原图是木有任何影响的。
2,对于仍存在的负边权,我们以虚拟节点为源跑一遍 Bellman—Ford 算法,求得 dis 数组。
3,接下来,我们要把所有边权变成适合 Dijkstra 宝宝的非负边权,新边权的求法公式如下:
4,重置边权后,再去对 n 个点跑优化后的 Dijkstra 算法,这里的复杂度是 。
3,正确性的证明
1,为什么原最短路径和边权重置后的最短路径是一个路径(路径正确性)?
首先,我们假设在原图中点 x 到点 y 有这么一条路径:
那么,原图路径和为下式(式子太长就写了开头和结尾的两项):
中间的省略部分也就是
全部加起来~,我们惊奇的发现结果w(゚Д゚)w
而省略部分是
所以,每条路径上都只加了两个常量,每条路径上的边权和的优劣性是不变的。
2,为什么新边权保证非负呢?
首先,有一个灰常简单的不等式:
毋庸置疑(●ˇ∀ˇ●)
那么移项一下,再结合刚才所说新边权的求法公式,我们又惊奇地发现:
这样就完美地证明啦!接下来只需要对每一个点跑一遍 Dijkstra 算法即可。
4,对于洛谷模板题
这道题题目说了有负环的呢,那我们 Johnson 算法的第一步 Bellman-Ford 就可以用来判断负环,直接输出 -1 结束。
而且题目要求我们每行只输出一个整数即可,因此不用开二维数组,可以求完一轮输出一轮的答案。实际上整个算法并不算很难,只是细节很多,比如函数对于数组的数据共享及繁琐的数组重置,下面贴上 AC 代码。
#include<iostream>
#include<vector>
#include<queue>
#include<climits>
#include<cstring>
#define MAXN 3005
#define int long long
#define INF 1e18
using namespace std;
struct edge{
int v;
int w;
};
struct node{
int dis;
int u;
bool operator>(const node& a)const{
return dis>a.dis;
}
};
int dis[MAXN];
vector<edge> e[MAXN];
int h[MAXN];
int cnt[MAXN];
bool vis[MAXN];
int n,m;
bool spfa(int s){
queue<int> q;
vector<int> dis1(n+2,INF);
vector<int> cnt1(n+2,0);
vector<bool> vis1(n+2,false);
dis1[s]=0;
q.push(s);
vis1[s]=true;
while(!q.empty()){
int u=q.front();
q.pop();
vis1[u]=false;
for(auto &i:e[u]){
int v=i.v;
int w=i.w;
if(dis1[v]>dis1[u]+w){
dis1[v]=dis1[u]+w;
cnt1[v]=cnt1[u]+1;
if(cnt1[v]>=n+1)return false;
if(!vis1[v]){
q.push(v);
vis1[v]=true;
}
}
}
}
for(int i=1;i<=n;i++){
h[i]=dis1[i];
}
return true;
}
void reset(){
for(int i=1;i<=n;i++){
for(auto &j:e[i]){//划重点,因为涉及修改,一定要加引用
j.w=j.w+h[i]-h[j.v];
}
}
}
void dijkstra(int s){
priority_queue<node,vector<node>,greater<node>> pq;
vector<bool> visited(n+1,false);
for(int i=1;i<=n;i++)dis[i]=INF;
dis[s]=0;
pq.push({0,s});
while(!pq.empty()){
int u=pq.top().u;
pq.pop();
if(visited[u])continue;
visited[u]=true;
for(auto &ed:e[u]){
int v=ed.v,w=ed.w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
pq.push({dis[v],v});
}
}
}
}
int read(){
int s=0,w=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')w=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
e[n+1].push_back({i,0});
}
int u,v,w_val;
for(int i=1;i<=m;i++){
u=read(),v=read(),w_val=read();
e[u].push_back({v,w_val});
}
if(!spfa(n+1)){
cout<<"-1";
return 0;
}
reset();
for(int i=1;i<=n;i++){
dijkstra(i);
int ans=0;
for(int j=1;j<=n;j++){
if(dis[j]>=INF/2){
ans+=j*1e9;
}
else{
int real_dis=dis[j]-h[i]+h[j];
ans+=j*real_dis;
}
}
cout<<ans<<"\n";
}
return 0;
}
看不懂来私信骂学姐!
587

被折叠的 条评论
为什么被折叠?



