浅谈有限制的最短路问题
AstaVenti_ · · 算法·理论
\mathbb{PART\ I.}\quad 路径本身有限制的最短路
这一部分大多是对松弛条件进行修改,使其在每一步都满足要求。
\color{#3498db}{\text{P13534 [OOI 2023] The way home}}
这一部分的题目一般会有所谓的「特殊边」,且数据范围尤其是特殊边的数量通常较小,这时候可以考虑分层图/类分层图。
\color{#3498db}{\fbox{分层图}}
\color{#3498db}{\text{P7297 [USACO21JAN] Telephone G}} \color{#3498db}{\tiny{\text{ 分层图的绝妙使用!}}}
给
::::success[Solution]
我们先考虑
- 如果
S_{1,1}=1 ,则1 可以到达n ; - 否则
S_{1,1}=0 ,1 不能到达n 。
注意到
对于一个位置
void upd(ll u,ll v,ll w){
if(dis[v])return;
if(w)q.push_back({v,dis[v]=dis[u]+w});
else q.push_front({v,dis[v]=dis[u]+w});
}
ll lyr=(x-1)/n,pos=(x-1)%n+1;
if(pos>1)upd(x,x-1,1);
if(pos<n)upd(x,x+1,1);
if(s[lyr][b[pos]])upd(x,pos,0);
:::info[*为什么这里不是
这样就可以建图了。还可以用 01BFS 优化,这样连图都不用建了。 ::::
\color{#3498db}{\text{P5122 [USACO18DEC] Fine Dining G}}
有
::::success[Solution] 在第一篇题解里面是这么说的(已经过意译,原文使用的变量名没有事先定义):
再对于每一次的干草堆操作,将一个虚拟节点与这个点之间建一条长度为(
N 到这个个点的距离减它的美味值)的边。
其实只要把这句话读明白,理解原因就能做出来了。下面来解释一下这段话:
这里的“美味值”本质上还是分层图,相当于这个节点可以把两点之间的最短路减去它的美味值。这里我们就可以把这一个点拆成两个节点,且这两个点之间只有一条边权为它的美味值的边。
但这样会有负环,而且还有可能不止一次地经过那些不同的被拆开的点,显然是无法跑 Dijkstra 的。考虑一种优化方法:假设我们已知
for(ll i=1,pos,x;i<=k;i++){
cin>>pos>>x;
e[0].emplace_back(pos,ans[pos]-x);
}
:::info[一个小技巧]{open} 如果要求多个点到同一个点的最短路,可以转换为在其反图上求这个点到多个点的最短路(当然这个题是双向边所以就没有额外建反图)。 ::: ::::
\color{#3498db}{\text{P13515 [KOI 2025 \#1] 木槿花开了}}
给定由
::::success[Solution]
注意到如果
具体地,对于每个
if(dis[i]<=a&&c[i]==0)dis[i]=0,q.emplace(i,0);
else dis[i]=LLONG_MAX-INT_MAX;
:::info[如何更新答案?]{open}
对于这个
\color{#3498db}{\fbox{化边为点}}
\color{#3498db}{\text{P11860 [CCC 2025 Senior] 熔岩路 / Floor is Lava}}
在图上求出一条
::::success[Solution]
考虑将边建成点。对于原图中一个点发出的所有边,将这些边两两连接,两个新点之间的边权即为原先两条边边权之差的绝对值。但这样有一个很显然的问题:如果图为菊花图(或类似),那么边的数量就会卡到
让我们考虑下面一张图:
可以发现,如果我们想从
\color{#3498db}{\text{CF1407E Egor in the Republic of Dagestan}}
有
::::success[Solution] 我们要尽量地让一条边起始点的颜色和边的颜色不一样。
对于每个没有染色的点,直接设置成与其不一样的颜色即可。
reverse(e[u].begin(),e[u].end());
for(auto [v,t]:e[u]){
if(vis[v])continue;
if(p[v]==-1||p[v]==t^1){
p[v]=t^1;
}else{
q.push(v);
dis[v]=dis[u]+1;
vis[v]=1;
}
}
:::info[如果你 WA on #29]{open} 题目本身很简单,但是有一个极其坑的点:
如果你在 CF 上交的结果是
wrong answer Answer must be equal to the value of shortest path's length: output is 138, should be 137
看一下你是否用的是 vector 存图。vector 的存图方式和链式前向星是相反的,因此只需要在遍历边之前加上 reverse(e[u].begin(),e[u].end()); 即可。
尽管目前仍未知道为什么链式前向星就能过。
::: ::::
\mathbb{PART\ III.}\quad 网格上的最短路建模
对于网格上的问题,大部分的边一般只会建在相邻的格子之间,如果题目中没有类似的要求,或要求输出方案等,就需要考虑使用 BFS 等方法。
\color{#3498db}{\text{P6233 [eJOI 2019] Awesome Arrowland Adventure}}
给你一个
::::success[Solution]
对于一个点,往其上、下、左、右四个方向建边,边权即为「这个点要指向这个方向需要旋转多少次」,然后从
\color{#3498db}{\text{P3036 [USACO16DEC] Lasers and Mirrors G}}
给定一个二维平面上的
::::success[Solution]
对于每个点,可以拆成上下左右四个方向的点。对于单点拆成的四个点中相反方向的点,不需要拐弯,连一条边权为
对于原图中同行同列的点,直接连权为
从起点跑一遍 Dijkstra 或者 01bfs 即可。 ::::
::::info[关于这一部分——网格上的最短路]{open}
其实很多网格上的问题由于一些限制,使用最短路并不能很好地处理,因此直接 BFS 反而会更优。以下是一些例子:
::::
\mathbb{PART\ IV.}\ 在特殊数据范围下最短路问题的特定解法
\color{#3498db}{\text{CF1051F The Shortest Statement}}
给你一个有
::::success[Solution] 极其冗长。极其繁琐。三个板子的结合。
注意到
考虑两个点
- 在它们最小生成树上,
u,v 到\operatorname{lca}(u,v) 的距离之和。 - 经过一个不在最小生成树上的点
k ,u,v 到k 的距离之和。
对于第一种情况,我们可以直接做一遍最小生成树并对其预处理,在
对于第二种情况,因为
核心代码如下:
G.Kruskal();
T.depth[1]=T.dep[1]=0,T.dfs(1,0),T.init();
for(ll i=1,u,v;i<=q;i++){
cin>>u>>v;
ll ans=T.getdis(u,v);
for(ll i=0;i<G.sp.size();i++){
ans=min(ans,G.dis[i][u]+G.dis[i][v]);
}
cout<<ans<<endl;
}
:::: 类似地,还有以下题目:
这一部分的建图主要与区间有关,由于对于区间中的每个数一一连边肯定不行,因此需要在线段树上对应节点建边。
\color{9d3dcf}{\text{CF786B Legacy}}
给定
- 操作1:建一条
v 到u 的边; - 操作2:建一条
v 到区间[l,r] 的边; - 操作3:建一条区间
[l,r] 到v 的边。
询问点
::::success[Solution] 区间问题,很容易想到线段树,所以我们用线段树来辅助建图。对于单点连边,我们直接连。对于区间问题,我们对于从区间到点和点到区间分别建一棵线段树,线段树的叶子节点为原来的点,其他节点对应区间。以点到区间为例,进行一次修改,从原来节点向树上对应区间节点建边即可,区间到点同理。 ::::
类似地,还有