[笔记]最短路问题的变形

\(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\)结束的最短路即可。

求最短路数量

P2047 [NOI2007] 社交网络

  • 对于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)\)的复杂度内解决。

P6175 无向图的最小环问题

对于一个最小环,删除其中的一个点,得到了简单路径一定是其端点之间的最短路。

所以我们枚举与\(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;
}
posted @ 2024-11-27 20:04  Sinktank  阅读(11)  评论(0编辑  收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2024 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.