[笔记]最短路问题的变形
求\(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;
}