图论其他算法
题单:点我。
二分图
……
k 短路
原理
k 短路问题一般使用 A* 算法解决,由于可持久化可并堆优化难度过高(黑题),本文将不会涉及相关内容。
A*,即启发式 bfs,依赖以下函数(设起点 \(s\),终点 \(t\))
- \(g(x)\),代表按照 \(x\) 状态的路径走,从 \(s\) 到 \(x\) 所在点的实际代价;
- \(h(x)\),代表 \(x\) 所在点到 \(t\) 的估计代价;
- \(h^*(x)\),代表 \(x\) 所在点到 \(t\) 的实际代价;
- \(f(x)=g(x)+h(x)\),为估价函数。
注意,本文中状态并非简单的一个点,而包含了该状态所在的点和从起点来该点的路径。路径信息可以直接通过存储 \(g(x)\) 代替,因为路径并不重要,重要的是路径长度。
初始时,以 \(s\) 构造状态,放入堆中。每次取出估价函数 \(f(x)\) 最小的状态扩展子状态,放入堆中。当取出的状态到达 \(t\) 时,即得到了最优解。
一般地,为了得到最优解,要求 \(\forall x,h(x)\le h^*(x)\),即估价函数必须乐观。为什么呢?设 \(d\) 为最优解(最短路长度),
- 对于最优路径上的任意状态 \(x\),\(d=g(x)+h^*(x)\ge g(x)+h(x)=f(x)\),即有 \(f(x)\le d\)。
- 而对于非最优路径上的任意状态 \(y\),必然存在 \(y\) 的子状态 \(z\),使得 \(f(z)=g(z)+h(z)>d\)。考虑状态 \(z\) 表示 \(y\) 的子状态,且 \(z\) 到达了 \(t\)(或到达 \(t\) 附近)。此时 \(f(z)=g(z)+h(z)>d+h(z)\),正常的有 \(h(z)=0\),故 \(f(z)>d\)。
也就是说,即使 \(z\) 被压入堆,也不会被取出,而会取出 \(x\)。
总之,只要 \(f(x)\) 乐观,就可以得到最优解。如果悲观,则可以快速求出没那么优的解。当然,如果 \(\forall x,h(x)=h^*(x)\),即可最快地稳定地求出最优解。估价函数应尽量接近实际代价。
类似的,可以证明到达每个点的第 \(k\) 小代价为 在该点的第 \(k\) 个出堆的状态的代价。人话:如果一个状态第 \(k\) 个到达某个点,它即为 \(k\) 短路。
那么怎么设计估价函数呢?
考虑在反向图上跑单源最短路,起点为 \(t\)。设 \(u\) 到 \(t\) 的最短路为 \(dis(u,t)\),估价函数即为 \(f(x)=g(x)+h(x)=g(x)+dis(x,t)\)。这里 \(dis(x,t)\) 中 \(x\) 实际上是 \(x\) 所在的点。也就是说,我们以到 \(t\) 的最短路作为 \(h(x)\)。事实上,dijkstra 实现单源最短路本质也是 A*,其中 \(h(x)\equiv 0\)。
实现
差分约束
引入
考虑单源最短路的一个性质:
在有向图中,若存在边 \(u\to v\),边权为 \(w\),则 \(dis_u+w\ge dis_v\)。
正确性是显然的,因为如果反之 \({dis}_u+w<dis_v\),我们就可以令 \({dis}_v\gets dis_u+w\),原先的就不是最短路了,与题设矛盾。
那么怎么使用这个性质呢?
正文
当题目中出现形如
\(m\) 个限制条件,\(n\) 个变量 \(x_i\),每个限制条件形如:\(x_a-x_b\le y\)。
时,就可以建图跑最短路。\(x_a-x_b\le y\implies x_b+y\ge x_a\),即 \(b\) 向 \(a\) 连边权为 \(y\) 的边。
例题
Luogu P5960 【模板】差分约束
直接连边跑最短路即可。但观察到无法快速确定一个源点,使得从它开始可以遍历到每个点。
技巧 \(1\):可以建立超级源点 \(0\),让 \(0\) 向每一个点连边。如果被卡可以 random_shuffle()
一下 \(0\) 的出边。
Luogu P3275 [SCOI2011] 糖果
依然考虑差分约束。
先使用技巧 \(1\),超级源点连所有点,边权为 \(1\),这样满足每个小朋友都可以分到糖果。
注意到边权只为 \(\{0,-1\}\),跑最短路可能使小朋友倒贴糖果 \(dis\) 为负。
当然,可以一负到底,将上面连超级源点的边权改成 \(-1\),然后输出 \(-dis(i)\) 即可。
技巧 \(2\):变正权边。
可以将边权取相反数,跑最长路。注意该技巧没有本质优化,不可以实现“变正权边跑 dijkstra”之类的操作。
这个技巧唯一的用处就是思路更顺。注意即使是“最长路”求出的未知数和照样最小,最长路只是保证约束条件。
但本题可以把队列优化 Bellman-Ford 卡爆!怎么办?
技巧 \(3\):配合 Tarjan 缩点。
以下题解使用了 技巧 \(2\),当然不用也可以做。
先缩点,每个 SCC 内部如果出现了一条 \(u\) 到 \(v\) 的边权为 \(1\),根据 SCC 的定义,一定还存在一条 \(v\) 到 \(u\) 的路径,由于边权 \(\ge 0\),所以一定会出现一个正权环,则这个差分约束系统无解。
否则的话,发现 SCC 内部变量取值一定是相同的,那么问题变成了一个 DAG 上最长路,拓扑排序即可。
——来自该题题解
Luogu P3530 [POI2012] FES-Festival
题意:\(x_a=x_b-1,x_c\le x_d\)。求 \(|\{x_1,x_2,\dots,x_n\}|\) 的最大值(即最大化 \(x\) 中的数的种数)。
考虑转化题意
- \(x_a=x_b-1\iff x_a\ge x_b-1\land x_a\le x_b-1\iff (a,b,1),(b,a,-1)\);
- \(x_c\le x_d\iff (d,c,0)\)。
首先要明确:\((a,b,w),(b,c,w')\implies (a,c,w+w')\),因为 \(dis(a)+w+w'\ge dis(b)+w'\ge dis(c)\)。即 \(dis(b)-dis(a)\le w+w'\)。更一般地,可以推广到任意条边。
考虑对于一般的差分约束系统,点与点的连通性非常混乱,难以出现很强的约束关系。考虑在强连通分量中,其中任意点 \(u,v\),显然有 \(x_v-x_u\le dis(u,v)\),同时 \(x_u-x_v\le dis(v,u)\),因为 \(u,v\) 双向可达。于是我们得到 \(-dis(u,v)\le x_u-x_v\le dis(v,u)\)。也就是说,\(x_u-x_v\) 的差有一个固定的范围。
考虑在数轴上的意义。一个强连通分量 \(S\) 中的点的未知数在数轴上必然有最大值和最小值,它们的差(极差)就是 SCC 内任意两点间最短路的最大值。因为 \(\max x_u-x_v\le\max dis(v,u)\),可以取等。于是我们得到了 SCC 内极差。
考虑 SCC 内最多取值数(SCC 内答案)。由于边权只取 \(\{-1,0,1\}\),显然有 \(\forall u,v\in S,dis(u,v)<|S|\)。所以 \(\max x_u-x_v<|S|\),也就是说数轴上 \([x_u,x_v]\) 的整点会被填得满满的。即,SCC 内答案为 \(\max x_u-x_v=\max dis(v,u)\)。
考虑原图。缩点后,图变成 DAG,且显然剩下的边边权为 \(0\)(第二种限制),反之可以继续缩点。也就是说,只剩下 SCC 间的 \(\le\) 关系。于是,我们可以令任意两个 SCC 在数轴上的距离无限拉大,这样各个 SCC 间就互相独立了。累加各个 SCC 的答案即为最终答案。