[笔记]最短路问题的变形
求\(s\)到\(t\)必须经过某个点/某条边的最短路
这个相当板子了,点\(u\)的答案是\(dis(s,u)+dis(u,t)\),边\(e=(u,v)\)的答案是\(\min(dis(s,u)+dis(v,t),dis(s,v)+dis(u,t))+w(e)\)。其中\(dis(u,v)\)表示\(u\)到\(v\)的最短路。
从\(s\)和\(t\)各跑一次Dijkstra,其中\(t\)用反图。预处理出从\(s\)出发和以\(t\)结束的最短路即可。
求最短路数量
- 对于Dijkstra,在松弛时,如果\(d[u]+w=d[i]\),则令\(cnt[i]\leftarrow cnt[i]+cnt[u]\);否则令\(cnt[i]\leftarrow cnt[u]\)。
- 对于Floyd,枚举\(i\)到\(j\)的中转点\(k\),如果\(d[i][k]+d[k][j]=d[i][j]\),则令\(cnt[i][j]\leftarrow cnt[i][j]+cnt[i][k]\times cnt[k][j]\);否则令\(cnt[i][j]\leftarrow cnt[i][k]\times cnt[k][j]\)。
- 说明:枚举\(k\)之前的最短路经过的结点都只经过\(1\sim (k-1)\)的点,所以不会重复统计。
Dijkstra 堆优化
struct edge{int to,w;}; vector<edge> G[N]; int d[N],cnt[N]; priority_queue<PII,vector<PII>,greater<PII>> q; void dijkstra(int s){ memset(d,0x3f,sizeof d); memset(cnt,0,sizeof cnt); d[s]=0,cnt[s]=1,q.push({0,s}); while(!q.empty()){ auto t=q.top(); int u=t.second; q.pop(); if(t.first>d[u]) continue; for(auto i:G[u]){ if(d[u]+i.w==d[i.to]) cnt[i.to]+=cnt[u]; if(d[u]+i.w<d[i.to]){ d[i.to]=d[u]+i.w; q.push({d[i.to],i.to}); cnt[i.to]=cnt[u]; } } } }
Floyd
for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(f[i][k]+f[k][j]==f[i][j]) cnt[i][j]+=cnt[i][k]*cnt[k][j]; else if(f[i][k]+f[k][j]<f[i][j]) f[i][j]=f[i][k]+f[k][j], cnt[i][j]=cnt[i][k]*cnt[k][j];
求两点间最大边权的最小值
并不是例题:P2245 星际导航
P2245的双倍经验:P1967 [NOIP2013 提高组] 货车运输
P2245的正解是(Kruskal重构树 / 最小生成树) + LCA,这样时间复杂度是\(O(m\log m)+O(n\log n)+O(q\log n)\),每次询问是\(O(\log n)\)的(当然也LCA可以离线求,复杂度上会更优一些)。
上面的题用最短路无法通过,但是在固定起点的情况下,Dijkstra可以做到\(O(m\log m)\)预处理,\(O(1)\)查询;需要任意两点间的答案时,Floyd可以\(O(n^3)\)预处理,\(O(1)\)查询。
- 对于Dijkstra,重新定义\(f[x]\)为到\(x\)的最大边权的最小值。松弛时,\(f[i]=\min(f[i],\max(f[u],w))\)。
- 初始化时\(f\)设为\(+\infty\),\(f[s]=-\infty\),优先队列按升序。如果是最小边权最大则反过来。
- 对于Floyd,重新定义\(f[i][j]\)为\(i\)到\(j\)的最大边权的最小值。转移时\(f[i][j]=\min(\max(f[i][k],f[k][j]))\)。
- 邻接矩阵无边处置为\(+\infty\)。如果是最小边权最大则反过来。
求每个点到最近关键点(可能有多个)的距离
P9432 [NAPC-#1] rStage5 - Hard Conveyors ~ 题解
初始化时,将所有关键点的\(d\)设为\(0\),其他设为\(+\infty\),正常跑Dijkstra即可。
对于树形结构,也存在非最短路的线性做法,见上面的题解。
只含\(0,1\)边权的图的最短路(0-1 BFS)
对于只含\(0,1\)边权(当然\(0\)和其他也是一样)的图,可以使用0-1 BFS求解最短路,时间复杂度是\(O(n+m)\)。
0-1 BFS本质是优先队列优化的Dijkstra,不过在边权只有\(0,1\)的情况下,优先队列中的距离集合最多就是相邻的两个整数。
所以我们不必采用优先队列,而是使用双端队列,如果用于松弛的边权值较大则放在队尾,否则放在队头,连pair
都不需要。和优先队列效果相同。
点击查看代码
deque<int> q; bitset<N*M> vis; void dijkstra(int s){ memset(d,0x3f,sizeof d); d[s]=0,q.push_back(s); while(!q.empty()){ int u=q.front(); q.pop_front(); if(vis[u]) continue; vis[u]=1; for(auto i:G[u]){ int td=d[u]+i.w,v=i.to; if(td<d[v]){ d[v]=td; if(i.w) q.push_back(v); else q.push_front(v); } } } }
最小环问题
哈密顿环问题是NPC的,而最大环问题是它的特例,所以最大环问题同样是NPC的,目前只通过暴搜解决。
不过如果规定每个点出度都为\(1\),这张图就是一个基环树森林,不存在环的嵌套,跑一边拓扑/Tarjan找出来单个的环,记录大小即可。
然而最小环问题可以用Floyd在\(O(n^3)\)的复杂度内解决。
对于一个最小环,删除其中的一个点,得到了简单路径一定是其端点之间的最短路。
所以我们枚举与\(i,j\)邻接的点\(k\),用\(v(k,i)+v(k,j)+f[k-1][i][j]\)更新\(ans\)即可。对于一个环,其答案会在其上编号最大的点处被准确计算出来。
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; int n,m; int G[110][110],d[110][110],ans=LLONG_MAX; signed main(){ cin>>n>>m; memset(d,0x3f,sizeof d); for(int i=1;i<=n;i++) d[i][i]=0; for(int i=1;i<=m;i++){ int u,v,w; cin>>u>>v>>w; d[u][v]=d[v][u]=G[u][v]=G[v][u]=w; } for(int k=1;k<=n;k++){ for(int i=1;i<k;i++){ for(int j=i+1;j<k;j++){ if(G[i][k]&&G[j][k]) ans=min(ans,d[i][j]+G[i][k]+G[j][k]); } } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(d[i][k]+d[j][k]<d[i][j]) d[i][j]=d[i][k]+d[j][k]; } } } if(ans==LLONG_MAX) cout<<"No solution.\n"; else cout<<ans<<"\n"; return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验