图论经典模型 听课笔记

下文主要梳理最短路的建图技巧,以及一些杂题。

1. 超级源点、超级汇点

模型 1.1(租车线路)
有若干条租车线路 (u,v,w)(w0),表示可以花 w 块钱租一辆从城市 u 到城市 v 的车。
现在有甲、乙两人,甲从 A 到 C,乙从 B 到 D。两人在途中可以停留,也可以共租一辆车。求最小租车费用。
n,m105

建图,连边 (u,v,w)
若两人的路径不交,分别从 A、B 出发,跑一边单源最短路即可。
否则,甲、乙两人的路径交集一定是连续的一段边,如下图:

(如果交集不连续,记最开始的公共点为 X,最后的公共点为 Y。分别统计一下甲、乙两人的路径中 X Y 的长度,将这两条路径中较长的路径替换成较短的路径(若长度相等,则替换其中任意一条路径),替换后答案一定不会更劣。)

dist(u,v) 表示图中 uv 的最短路径长度。则答案为 dist(A,X)+dist(B,X)+dist(X,Y)+dist(Y,C)+dist(Y,D) 的最小值。
f(u)=dist(A,u)+dist(B,u)g(u)=dist(u,C)+dist(u,D),则答案为 f(X)+dist(X,Y)+g(Y)
建反图,在原图上跑一遍单源最短路即可求得 f(u),在反图上跑一遍单源最短路即可求得 g(u)。因此 f(u)g(u) 均可 Θ(mlogn) 预处理得到。
因此,只需在原图上新建超级源点 S 和超级汇点 T,对原图上每个点 u 连边 (S,u,f(u))(u,T,g(u)),在新图上跑一遍 ST 的最短路即可求得答案。

时间复杂度为 Θ(mlogn)

模型 1.2
给定点集 S 和 T,要求从 S 中的每个点向 T 中的每个点连边权为 w 的边,如何处理?

如果直接暴力连边,边数的复杂度为 Θ(|S||T|)
但我们新建一个点 p,对 S 中的每个点 u 连边 (u,p,w),对 T 中的每个点 v 连边 (p,v,0),即可将复杂度优化至 Θ(|S|+|T|)

模型 1.3
k 次连边,每次从编号在 [l1,r1] 中的点向编号在 [l2,r2] 中的点连边权为 w 的边,如何处理?

考虑线段树优化建图。建两颗线段树 s1s2,在 s1 中连儿子指向父亲、边权为 0 的边,在 s2 中连父亲指向儿子、边权为 0 的边。
每次连边时,新建节点 p,在 s1 中找出 [l1,r1] 对应的 Θ(logn) 个区间节点,对其中每个点 u 连边 (u,p,w);在 s2 中找出 [l2,r2] 对应的 Θ(logn) 个区间节点,对其中每个点 v 连边 (p,v,0)
时间复杂度为 Θ(n+klogn),空间复杂度为 Θ(n+klogn)

2. 单源最小环

模型 2
n 个点、m 条边的带边权无向图中,包含 1 号点的最小环大小。

如何做到 Θ(nmlogn)

对与 1 号点相连的每条边 e=(1,u,w),将边 e 断掉后跑一遍从 u1 的最短路 d,则 w+d 的最小值即为答案。

如何做到 Θ(mlog2n)

对原图 G 中任意两个相邻的点 uv,记 w(u,v) 表示 uv 之间的边的长度。
考虑在原图 G 中,将 1 号点和与 1 号点相连的边删去,得到图 G'。
考虑 G 中每一个包含 1 号点的环,令环中与 1 号点相邻的点为 uv,则必有 uv。在图 G 中,求出 uv 两点间的最短路 dist(u,v)。则答案为 w(1,u)+dist(u,v)+w(1,v)

容易发现这与模型 1.1 中的表达式类似。于是考虑在图 G' 中新建源点 S 和汇点 T,对 G' 中每个点 u有向(S,u,w(1,u))(u,T,w(1,u)),希望新图中 ST 的最短路为答案。
但这么做会带来一个问题:可能求出的最短路为 SuT,对应原图 G 中的方案即为 1u1。这相当于在一条无向边上来回横跳,不符合环的定义。
为避免这种情况发生,考虑将点 2n 分成两组,其中一组与 S 相连,另一组与 T 相连。希望新图中 ST 的最短路为答案。
但这种做法依然存在问题。记实际的最小环中,与 1 相连的点为 uv。若 uv 恰好被分到同一组中,则这种做法无法求出真正的最短路。

考虑到 uv,即 uv 的二进制表示中必有一位不同。因此可以将新图拆成 Θ(logn) 个图,从 0 开始标号。在第 i 个图中,将所有二进制表示下第 i 位(将最低为编号为 0)为 0 的点与 S 相连,第 i 位为 1 的点与 T 相连。
对所有 Θ(logn) 个图都跑一遍最短路。由于 uv 的二进制表示中必有一位不同,因此总有一张图中 uv 被分到不同组中,因此总能求出正确答案。对求出的所有答案取最小值即可。

如何做到 Θ(mlogn)

在原图 G 中将 1 号点删去,得到新图 G'。在 G' 中加入超级源点 S。对 G 中 1 号点的所有邻点 u,在 G' 中连边 (S,u,0)
在图 G' 中从 S 出发跑一遍单源最短路。在算法中,对每个点 u,记录 Su 的最短路径中 S 的邻点 gu(最短路记为 fu)。

观察 G 中每个经过 1 的环,在 G' 中对应一条 S 的邻点 uv 之间的最短路径,且其关系为环长 l=w(1,u)+dist(u,v)+w(1,v)。由于 gu=u, gv=v(即 gugv),因此在 uv 间的最短路径中,必然存在一条边 (s,t),满足 gsgt

因此,只需枚举所有满足 gpgq 的边 (p,q),求出 w(1,gp)+w(1,gq)+w(p,q)+fp+fq 的最小值即可。(由上面的分析,这样一定能把经过 1 号点的环枚举完。)

做法来源:洛谷 P5304 [GXOI/GZOI2019] 旅行者

3. 分层图最短路

模型 3.1(跑图)
选择一条从 1 出发的路径,最小化“路径长度 减 路径上前 k 大的点的点权和”的值。
n,m105k10

发现将“路径上前 k 大的点”的条件泛化成“路径上任意 k 个点”并不影响答案。因为如果选择的点不是前 k 大的,则减数变小,答案变大,显然与“最小化”矛盾。
fu,i 表示在路径 1u 上,已经减了 i 个点的点权,所得的答案的最小值。枚举 u 的每条出边 (u,v,w),则有转移:

fv,i=min{fv,i,fu,i+w}

fv,i+1=min{fv,i+1,fu,i+wav}

枚举 i,对每个 i 跑一遍单源最短路即可,称之为“分层图最短路”。

模型 3.2(P3953 [NOIP2017 提高组] 逛公园)
给定带边权的有向图,记从 1 号点到 n 的最短路为 dist(1,n)
统计从 1n,且长度在 [dist(1,n),dist(1,n)+K] 的范围内的路径条数,答案对 P 取模。
若有无数条符合要求的路径,输出 1
n105m2×105K501P109,所有边权非负。

首先考虑边权均为正数的情况。
从 1 号点出发求单源最短路,记从 1 到 u 的最短路为 dist(1,u)
fu,i 表示从 1 到 u 的路径中,长度为 dist(1,u)+i 的路径条数。
枚举 u 的每条出边 (u,v,w),令 s=dist(1,u)+i+wdist(1,v),则有转移:

fv,s=(fv,s+fu,i)modP

由于求最短路后,一条边 (u,v,w) 必然满足 dist(1,u)+wdist(1,v),因此必有 si
容易发现这与模型 3.1 类似。因此,类似分层图最短路,我们枚举 i,对每个 i 按合理的顺序枚举节点,并 DP 即可。
考虑怎么确定这个顺序。对所有 s>i 的情况,v 所在的层必然在 u 的后面被枚举到,因此只需考虑 s=i 的情况。
s 的定义式变形,发现一定有 dist(1,u)+w=dist(1,v)。限制即对所有这种边,要求 uv 之前被枚举。
因此只需建出最短路图,并对其做拓扑排序即可。

但原题中还存在边权为 0 的情况。对一个每条边边权均为 0 的环,我们称为“零环”。
若一条路径经过零环上的任意一点,可以从该点出发绕零环走任意多圈再继续往下走,得到无数条路径。因此,只要有一条符合题目要求的路径经过零环上的点,则答案为 1
可以先通过一遍 dfs 求出所有零环上的点,则问题转化成是否存在在一条符合要求的路径经过零环上的点。
新建数组 {curn},其中 curu 表示只考虑枚举到的点构成的、经过零环上点的路径,从 1u 的最短路。
初始时,对所有零环上的点 u,有 curu=dist(1,u);对其他点 v,有 curv=+
然后从这些点出发跑一遍 Dijkstra,记 n 号点处的最短路为 curn。只需判断是否满足 curn>dist(1,n) 即可。

4. 单源次短路

次短路分为两类,严格次短路和非严格次短路。非严格次短路径不能与最短路径相同,但是长度可以与最短路相等。而严格次短路径的长度必须严格小于最短路。

如何求出次短的简单路径?

众所周知,一定存在一个方案,使得最短路径为简单路径,但次短路径不一定。下文旨在说明,在所有的简单路径中,如何求次短路径。
下面的例题给出了一个求非严格次短简单路径的方法。

例题 4.1(洛谷 P1491 集合位置)
给定一个带边权的无向图,图中没有重边,但可能有自环。求图中从 1 号点到 n 号点的非严格次短简单路径的长度。
若该路径不存在,输出 1
1n2001m10000,边权非负。

显然,由于最短路径和次短路径均为简单路径,因此最短路径中一定存在一条边,使得它在次短路径中未出现过。
因此做法呼之欲出。先跑一遍单源最短路,并记录最短路径经过的边。
考虑枚举最短路径中的边。对枚举的每条边,将其在原图中删去,并再跑一边单源最短路。(删边相互独立)
对枚举得到的答案求最小值,即为非严格次短简单路径的长度。
若使用朴素的 Dijkstra 算法,复杂度为 Θ(n3)
若使用堆优化的 Dijkstra 算法,复杂度为 Θ(nmlogn)

至于如何求出严格的次短简单路径,本人不会,因此略。

上文说明了对所有简单路径,如何求次短路。下文将探讨对所有路径,如何求次短路。

如何求出次短路径?

下面的例题给出了一个求严格次短路径的方法。

例题 4.2(洛谷 P2865 [USACO06NOV] Roadblocks G)
给定一个带边权的无向图,求图中从 1n 严格次短路径的长度。
1n50001m105,边权均为正数。

考虑对 Dijkstra 算法进行变形。开两个数组 dis1dis2,分别表示在当前所有枚举到的点构成的从 1n 的路径中,最短路和次短路的长度。
对当前正在枚举的点 u,考虑它的出边 (u,v,w)

  • dis1u+w<dis1v,则令 dis2v=min{dis1v,dis2u+w}dis1v=dis1u+w
  • 否则,若 dis1u+w=dis1v,令 dis2v=min{dis2v,dis2u+w}
  • 否则,令 dis2v=min{dis2v,dis1u+w}

但这样做会带来问题。考略 Dijkstra 的正确性证明,算法当前枚举的点 u 满足 dis1u 是当前所有未更新的点中最小的。当用 u 更新 v 时,由于边权非负,更新答案后的 v 也必然满足 dis1v>dis1u,因此 u 的答案不会更优。
但我们进行上述算法时,却只考虑了 dis1u 是当前所有未更新的点中最小的,却未保证 dis2u 是次小的。即,在更新完其他点之后,dis2u 的值可能变得更小,因此这个贪心策略就是错误的。

考虑改进上述算法。发现 dis1dis2 的操作很像分层图最短路。因此我们仿照分层图最短路,先对 dis1 进行一遍上述算法,同时更新 dis2 的“初值”;再对 dis2 跑一遍 Dijkstra,答案即为 dis2n 的值。
若使用朴素的 Dijkstra,时间复杂度为 Θ(n2)
若使用堆优化的 Dijkstra,时间复杂度为 Θ(mlogn)

非严格次短路的做法也类似,只需将上述分类讨论的第 2 条删去即可。

5. 建图优化 - 优化边数

有一些题目,通常给你 m 条特殊边,再给你一个规律让你生成很多边,让你在建成的图上跑最短路等算法。
如果直接建图,复杂度会炸掉。此时一般有两种思路:

  1. 使用数据结构维护。如线段树优化建图。
  2. 将图中冗余的边删去。即只用较少的边来等效地建出原图的很多边,常结合加点思想。

其中第 2 项是本文说明的重点。下面有 3 个例题,希望对以后做题有一定帮助。

例题 5.1
给定带边权的无向图,再给定常数 C,让你对每对点 (i,j) 连边权为 C×|ij| 的边,求单源最短路。
如何建图?
n,m106

显然只需在每一对 (i,i+1) 间连一条边权为 C 的无向边即可。
连边复杂度为 Θ(n)

例题 5.2
给定带边权的无向图,再给定常数 C,让你对每对点 (i,j) 连边权为 C×(ij) 的边,求单源最短路。
如何建图?( 表示按位异或)
n,m105

模仿例题 5.1 的做法。只需枚举每个点 i 和数位 j,连有向 / 无向边 (i,i2j,C×2j) 即可。
连边复杂度为 Θ(nlogn)

例题 5.3
给定带边权的无向图,再给定常数 C,让你对每对点 (i,j) 连边权为 C×(ij) 的边,求单源最短路。
如何建图?( 表示按位或)
n,m105

仿照例题 5.2 的做法,显然是行不通的。
考虑原图中一条从 uv 的边,在新图中被表示成一条 uuvv 的路径。当 iu 遍历到 uv 时,i 的二进制表示中不断增加 1;当 iuv 遍历到 v 时,i 的二进制表示中不断减少 1,方案如下图。

由于 uv 与边权有关,我们希望 uv 的路径上,(u,v) 的边权在 uv 处更新。考虑拆点,将每个点 u 拆成 u1u2,并实现如下建图方式:

  • 对每个点 u,连边 (u1,u2,C×u)(u2,u1,0)
  • 对每个点 u,枚举每个二进制位 i(由低到高,从 0 开始编号)。
    • u 的第 i 位为 0,连边 (u1,(u2i)1,0)
    • u 的第 i 位为 1,连边 (u2,(u2i)2,0)

具体而言,可以参考下图(图中省略角标)。

连边复杂度为 Θ(nlogn+n)

6. 次小生成树

有很多算法可以求最小生成树:Prim、Kruskal、Boruvka……但我们要求次小生成树。
由定义,次小生成树一定与最小生成树不同。
考虑用 Kruskal 算法求出一个最小生成树的边集 E1。则存在如下结论:

定理 6.1
存在一个次小生成树的边集 E2,使得 E1E2 只有一条边不同。

对非严格次小生成树,考虑所有在 E2 中出现过、但在 E1 中未出现过的边构成的集合 E3。则 E3 在生成树 E1 上一定构成了一些环。
由 Kruskal 算法的正确性证明,对于每一个环,E1 中的边的边权一定不大于 E3 中的边。
因此如果 |E3|>1,将 E3 中的某些边替换成 E1 中对应环上的边,答案一定不会更劣。因此替换后仍是非严格次小生成树。

对严格次小生成树,考虑在上述过程出现的环中,一定存在一条 E3 中的边,使得它的边权严格大于 E1 中对应环上的所有边。
因此如果 |E3|>1,将这条边保留,按上述方法把 E3 中的其他边全部换掉,答案一定不会更劣。因此替换后仍是严格次小生成树。

综上,无论要求非严格次小生成树还是严格次小生成树,定理 6.1 均成立。

有了定理 6.1,次小生成树就好求了。
对非严格次小生成树,首先用 Kruskal 算法求出最小生成树(边权和为 ans),然后枚举每条非树边 (u,v,w),用 LCA 查询树上 uv 的路径中边权最大的边 (p,q,r),求 ansr+w 的最小值即可。
对严格次小生成树,在上述算法中,将用 LCA 求最大值的操作改成最大值和次大值一起求,边权记为 r1>r2。求最小值时,若 r1=w 就用 r2 更新答案,否则用 r1 更新答案即可。
具体实现可以参考例题:洛谷 P4180 [BJWC2010] 严格次小生成树

7. 综合应用

例题 7.1 (洛谷 P4899 [IOI2018] werewolf 狼人)
给定 n 个点、m 条边的无向连通图,和 q 次询问。
每次询问给定四个点 STlr,你需要判断是否存在一条路径 SuT,使得 Su 上的每一个点(包括端点)编号均在 [l,n] 内,uT 上的每一个点(包括端点)编号均在 [1,r] 内。
2n2×105n1m4×1051q2×1051lrn

考虑 Kruskal 算法的流程:若当前枚举到的树边为 (u,v,w),则从 u 所在的子树到达 v 所在的子树,经过的边的最大边权至少为 w。(因为在所有边权比 w 小的边中(即枚举这条边之前),u 所在的子树和 v 所在的子树不连通。)
因此考虑构建 Kruskal 重构树。由上面的分析,若要求边权不超过 w1 时点 u 能到的所有点,则考虑在重构树中点权不超过 w1 且深度最小的 u 的祖先 x,以 x 为根的子树即为 u 能到达的所有点。(由建树过程不难证明)

考虑原问题,显然可以构建 Kruskal 重构树。
对于 Su,考虑给每条边 (u,v)(u<v) 赋边权 u,然后用 Kruskal 跑最大生成树。
对于 uT,考虑给每条边 (u,v)(u<v) 赋边权 v,然后用 Kruskal 跑最小生成树。
则问题转换为在两颗 Kruskal 重构树的某两个子树中,点集是否有交集。

考虑对每颗重构树求 dfs 序,这样保证了每颗子树的节点编号连续。
将每个结点映射到平面直角坐标系中,横、纵坐标分别对应两颗树上的 dfs 序,则问题转化为二维数点问题。
用树状数组离线处理即可。时间复杂度为 Θ(mlogm+n+qlogn)

本文中的题目

posted @   kilomiles  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示