当前位置: 首页 > news >正文

【题解】Luogu P5905【模板】全源最短路(Johnson)

思路分析

1. 判断负环

这个部分相对来说比较简单,可以用经典的 SPFA 算法判断负环,时间复杂度 \(O(nm)\)。这里给出一种效率更高的负环判断方法,使用 DFS 判断负环。遍历这个图,进行松弛技术,如果一个节点被访问第二次,那么就说明存在负环,直接退出。时间复杂度 \(O(n)\)

void fuhuan(int s){vis[s]=1;for(int i=h[s];i!=-1;i=e[i].nxt){if(d[e[i].to]>d[s]+e[i].w){if(vis[e[i].to]||flag){flag=1;break;}d[e[i].to]=d[s]+e[i].w;fuhuan(e[i].to);}}vis[s]=0;
}

如果找到了负环直接输出 \(-1\),跳出。

2. 新建节点,找最短路

这一部分是在为后面的算法核心做预处理。新建一个 \(0\) 节点连到所有节点上,然后从 \(0\) 节点跑一遍 SPFA 找到 \(0\) 距离所有节点的最短路。时间复杂度 \(O(nm)\)

void SPFA(int s){memset(d,0x3f,sizeof(d));memset(vis,0,sizeof(vis));d[s]=0;q.push(s);vis[s]=1;while(!q.empty()){int t=q.front();q.pop();vis[t]=0;for(int i=h[t];i!=-1;i=e[i].nxt){int v=e[i].to;if(d[v]>d[t]+e[i].w){d[v]=d[t]+e[i].w;if(vis[v]==0){q.push(v);vis[v]=1;}}}}
}
for(int i=1;i<=n;i++){Add(0,i,0);
}
SPFA(0);

3. 重置边权

这一部分是 Johnson 算法的核心,目的是使用更高效的 Dijkstra 算法求带有负边权的最短路。怎么实现呢?我们从 \(0\) 节点跑过一次最短路。设 \(u\) 节点到 \(0\) 节点的最短路是 \(h_u\),与 \(u\) 相连的节点是 \(v\)。因为 \(h_u\)\(h_v\) 都是松弛技术得来的最短路,那么对于原图中的边权 \(w(u,v)\),则有 \(h_u+w(u,v) \ge h_v\)。那么 \(h_u - h_v\) 就大于等于 \(-w(u,v)\),也就是原边权的相反数。于是把每条边的边权全部加上 \(h_u-h_v\),就可以保证边权全部非负,可以使用 Dijkstra 算法求解最短路。

为什么这么处理是正确的呢?我们不妨把这样求解的一条最短路展开,其中 \(s\) 为起点,\(e\) 为终点,\(p_i\) 是中间经过的点,共经过了 \(k\) 个点,可得:

\[(w(s,p_1)+h_s-h_{p_1})+(w(p_1,p_2)+h_{p_1}-h_{p_2})+...+(w(p_k,e)+h_{p_k}-h_e) \]

化简整理,得:

\[w(s,p_1)+w(p_1,p_2)+...+w(p_k,e)+h_s-h_e \]

可以发现,所有中间的值都约掉了,只剩下 \(h_s-h_e\) 这一个值。那么它就相当于一个偏移量,不影响最短路求解。

此部分时间复杂度为 \(O(m)\)

for(int i=1;i<=m;i++){e[i].w=e[i].w+d[e[i].from]-d[e[i].to]; 
}

4. 求解最短路

使用 Dijkstra 的堆优化对每个节点分别求解单元最短路,套板子即可。时间复杂度 \(O(nm \log m)\),优于 SPFA 的 \(O(n^2m)\),而且 SPFA 经常会被卡退化成 Bellman-Ford,变成 \(O(n^3m)\)

void dijkstra(int s){memset(vis,0,sizeof(vis));d2[s][s]=0;q2.push((Heapnode){0,s});while(!q2.empty()){Heapnode front=q2.top();q2.pop();int w=front.w,u=front.u;if(vis[u]) continue;vis[u]=1;for(int i=h[u];i!=-1;i=e[i].nxt){int v=e[i].to;if(d2[s][v]>d2[s][u]+e[i].w){d2[s][v]=d2[s][u]+e[i].w;if(!vis[v]){q2.push((Heapnode){d2[s][v],v});}}}}
}

5. 统计答案,输出

按题意来即可。记得减去先前的偏移量。

for(int i=1;i<=n;i++){ans=0;for(int j=1;j<=n;j++){if(d2[i][j]==0x3f3f3f3f3f3f3f3f){ans+=1000000000*j;}else{ans+=(d2[i][j]-d[i]+d[j])*j;}}printf("%lld\n",ans);
}

完整代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#define int long long
using namespace std;
const int maxn=3e3+10;
const int maxm=6e3+10;
struct Node{int from,to,nxt,w;
}e[maxm+maxn];
int n,m;
int h[maxn],tot,ans;
int d[maxn],d2[maxn][maxn],iqcnt[maxn];
bool vis[maxn],flag;
queue<int>q;
struct Heapnode{int w,u;bool operator <(const Heapnode &x) const{return w>x.w;}
};
priority_queue<Heapnode>q2;
void Add(int u,int v,int w){tot++;e[tot].from=u;e[tot].to=v;e[tot].nxt=h[u];e[tot].w=w;h[u]=tot;
}
void fuhuan(int s){vis[s]=1;for(int i=h[s];i!=-1;i=e[i].nxt){if(d[e[i].to]>d[s]+e[i].w){if(vis[e[i].to]||flag){flag=1;break;}d[e[i].to]=d[s]+e[i].w;fuhuan(e[i].to);}}vis[s]=0;
}
void SPFA(int s){memset(d,0x3f,sizeof(d));memset(vis,0,sizeof(vis));d[s]=0;q.push(s);vis[s]=1;while(!q.empty()){int t=q.front();q.pop();vis[t]=0;for(int i=h[t];i!=-1;i=e[i].nxt){int v=e[i].to;if(d[v]>d[t]+e[i].w){d[v]=d[t]+e[i].w;if(vis[v]==0){q.push(v);vis[v]=1;}}}}
}
void dijkstra(int s){memset(vis,0,sizeof(vis));d2[s][s]=0;q2.push((Heapnode){0,s});while(!q2.empty()){Heapnode front=q2.top();q2.pop();int w=front.w,u=front.u;if(vis[u]) continue;vis[u]=1;for(int i=h[u];i!=-1;i=e[i].nxt){int v=e[i].to;if(d2[s][v]>d2[s][u]+e[i].w){d2[s][v]=d2[s][u]+e[i].w;if(!vis[v]){q2.push((Heapnode){d2[s][v],v});}}}}
}
signed main(){memset(h,-1,sizeof(h));memset(d,0x3f,sizeof(d));scanf("%lld%lld",&n,&m);for(int i=1;i<=m;i++){int u,v,w;scanf("%lld%lld%lld",&u,&v,&w);Add(u,v,w);}for(int i=1;i<=n;i++){if(!vis[i]) fuhuan(i);}if(flag){printf("-1");return 0;} for(int i=1;i<=n;i++){Add(0,i,0);}SPFA(0);for(int i=1;i<=m;i++){e[i].w=e[i].w+d[e[i].from]-d[e[i].to]; }memset(d2,0x3f,sizeof(d2));for(int i=1;i<=n;i++){dijkstra(i);}for(int i=1;i<=n;i++){ans=0;for(int j=1;j<=n;j++){if(d2[i][j]==0x3f3f3f3f3f3f3f3f){ans+=1000000000*j;}else{ans+=(d2[i][j]-d[i]+d[j])*j;}}printf("%lld\n",ans);}return 0;
} 

一些坑点

  • 建完 \(0\) 点的所有边后边的总量是 \(n+m\) 条,空间别开小了(本人曾喜提 \(76\) pts RE);
  • long longmemset 初始化,最后判断的时候记得输对(比如 0x3f 就不是 int 类型的 0x3f3f3f,而是 0x3f3f3f3f3f3f3f3f)。

总结

这个题考察了几个求最短路的模板,以及修改负权的方法。虽然实际情况使用不多,但是仍然给我们提供了一条宝贵的思路。

总时间复杂度 \(O(nm \log m)\)

http://www.jsqmd.com/news/79235/

相关文章:

  • 基于SpringBoot的宠物识别小程序的设计与实现毕业设计项目源码
  • 基于SpringBoot的传统服饰订制系统毕业设计项目源码
  • 美团原生AI编辑器
  • 基于SpringBoot的大学生体测数据管理系统毕业设计项目源码
  • P3258 [JLOI2014] 松鼠的新家
  • K8S 中使用 YAML 安装 ECK
  • 如何更详细地应用AI提升学习效率?——大学生实战指南
  • 2025 最新租房/找房平台 TOP4 评测!数智化赋能 + 全维服务权威榜单发布,重构居家生活服务新生态 - 全局中转站
  • 当电机开始“唱歌“:NVH工程师的降噪日常
  • 在写小故事
  • Linux 中如何将文本中连续的字段转换成一个字段显示
  • 光伏板太阳能充电MATLAB仿真探索
  • 26、端口敲门与单包授权:网络安全认证技术对比
  • QtCreator IDE中向项目添加ui文件并绑定类
  • PI + 重复控制的并联型APF有源电力滤波器仿真探索
  • 20、深入理解Snort规则选项与iptables数据包过滤
  • 教程 29 - 从磁盘加载纹理
  • 从自动化到智能化,构建企业级Workflow Agent系统实战指南
  • 基于SpringBoot的大学生在线考试平台的设计与实现毕业设计项目源码
  • 003-RSA魔改:一号店
  • 创维LB2004_瑞芯微RK3566_2G+32G_删除移动定制_安卓11_原生桌面_线刷固件包-方法4
  • Jeecg AI开源平台:零门槛构建AI应用的完整指南
  • 与AI共舞:当代大学生如何在智能时代重塑学习与成长
  • RPA在企业微信桌面端的元素识别:基于坐标与基于属性的优劣对比
  • 详细介绍:【分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
  • 【Java避坑】为什么我的 String a == b 返回 false?一文搞懂 Java 中的 == 与 equals
  • 教程 30 - 纹理系统
  • 【题解】Luogu P1310 [NOIP 2011 普及组] 表达式的值
  • Java面试三连击:原理拆解+实战避坑
  • 【题解】Luogu P11854 [CSP-J2022 山东] 宴会