「专题落实」图论 - zsy
最小生成树
- 所有极小生成树都是最小生成树,这里的极小指的是无法通过更换一条边(从生成树中删去一条边,再加入一条边)使树边权值和变小。
- 最小生成树还有一个性质是,对于一张图的所有最小生成树(可能有多棵)来说,它们同种边权的边的数量是一定的,且在只考虑边权不超过某个阈值 \(T\) 的所有边时,任意两点间的连通性是相同的。
Borüvka 算法:
维护图中的连通块,每轮对每个连通块找出连向外的最小边权的边,尝试把这些边加入最小生成树,如此操作,在每一轮后连通块数量至少减半。时间复杂度为 \(\mathcal{O}(nk\log n)\),其中 \(k\) 为“对每个点找连向所在连通块外的最小边权的边”的复杂度。
这种算法一般用于求解(隐式给出的)完全图的最小生成树。
例题 1:「CF1550F」Jumping Around
考虑 Borüvka 算法,我们把两个联通块联通所需要的最小灵活度称为边权,每次对于每个联通块找到最小边,然后构建最小生成树。
最后在最小生成树上 DFS 一遍,就可以找到从根节点到达一个点所需要的最小灵活度。
代码链接:https://codeforces.ml/contest/1550/submission/146294686。
例题 2:位运算最优生成树
description
给出 \(n\) 个数 \(\{a_i\}_{i=1}^n\),建立一张完全图,边 \((i,j)\) 的边权是 \(a_i \oplus a_j\),其中 \(\oplus\) 是任意一个满足交换律的位运算 (通过每一位上的真值表来给出)。
求这张图的 \(\text{optimal = \{maximal,minimal\}}\) 生成树。
derivatives
\(\oplus = \text{bitwise xor,optimal = minimal} \Rightarrow \text{CF888G Xor-MST}\)
\(\oplus = \text{bitwise and,optimal = maximal} \Rightarrow \text{UOJ176}\) 新年的繁荣
solution
考虑一种统一的做法,考虑使用 Borüvka 算法。我们每轮需要做的事情是对每个 \(a_i\),找到一个异色的(不在同一个连通块内)\(j\) 来最大/小化 \(a_i \oplus a_j\)。
位运算可以贪心,按照二进制位从高到低,我们每次希望当前的最高位能(填 1/填 0/填什么都行)。建立一棵 Trie 树,这样“填 1/填 0”只需要向特定的一边递归即可,而“填什么都行”就比较麻烦,需要预先处理一个 Trie 树合并,从而让往一边递归实际上
起到了往两边递归的效果。需要注意的是 Trie 树每个节点上需要维护其子树内的两种不同颜色(因为要找且仅要找异色)。该算法的时间复杂度为 \(O(n\log n\log a_i)\)。
对于 CF888G,只需要在并查集合并的时候顺便也将 trie 树合并上去。
对于 UOJ176,可以每次进行 Borüvka 的时候重新建树,然后将右子树合并到左子树上就行了。
Code for CF888G:https://codeforces.ml/contest/888/submission/146298188。
Code for UOJ176:https://uoj.ac/submission/535053。
最短路
例题 3:「Gym100624F」「CERC2012」Farm and Factory
description
有一张 \(n\) 点 \(m\) 条边的无向图,你需要新增一个点并向其余的部分点连边(边权可以是任意实数),使得任何其他的点到 \(1,2\) 两个点的最短路都 可以不经过 这个新点,要求最小化新点到其余每个点的距离之和。
constraint
\(n \le 10^5, m \le 3 \times 10^5\)。
solution
这个限制实际上是指加入这个新点后,\(1,2\) 两个点到其他点的最短路不变。
设每个点 \(i\)(自然不包括新点)到 \(1\) 的最短路是 \(f_i\),到 \(2\) 的最短路是 \(g_i\),到新点的最短路是 \(s_i\),我们可以得到对于每个 \(i\),都有 \(<f_i,s_i,s_1>,<g_i,s_i,s_2>\) 这两个三元对满足三角不等式(即任两者之和不小于第三者)。
因为要最小化 \(\sum s_i\),所以我们令 \(s_i = \max\{|f_i-s_1|,|g_i-s_2|\}\) 一定不会劣。
所以我们只需要确定 \(s_1,s_2\) 的取值,然后最小化 \(\sum\max\{|f_i-s_1|,|g_i-s_2|\}\) 就可以了。这个形式恰好是点 \((s_1,s_2)\) 与点 \((f_i,g_i)\) 的 Chebyshev 距离,只需要先转成 Manhattan 距离(这样两维就独立了),然后对两维坐标分别取中位数即可。
代码链接:https://paste.ubuntu.com/p/x9WHvf6bGt/。
例题 4:「洛谷 P2371」 [国家集训队]墨墨的等式
大概是同余最短路的模板题。
选 \(a_1\) 作为基,显然若 \(b\) 满足条件,则 \(b + a_1\) 也满足。
记 \(f_i\) 表示用 \(a_2,a_3,\dots,a_n\) 能凑出的最小的模 \(a_1\) 等于 \(i\) 的数,这部分可以使用最短路算法解决。求出了 \(f_i\) 后也便不难求出 \([l,r]\) 中合法 \(b\) 的数量。
代码链接:https://paste.ubuntu.com/p/3DzhBTDkn9/。
Tarjan
一般来说包含求 (无向图) 点双连通分量、边双连通分量以及 (有向图) 强连通分量。
算法流程大致可以概括为:建立一棵 DFS 树,对每个节点记录访问时间 \(dfn_i\) 以及其 DFS 树子树内节点通过一条返祖边能够到达节点的最小 \(dfn\)。注意在无向图中只存在树边和返祖边,有向图中存在树边、返祖边以及横跨边。
算法竞赛领域对 Tarjan 相关算法的考察点一般为:
点双连通分量 \(\to\) 圆方树 \(\to\) 相关树上算法;
边双连通分量 \(\to\) 树 \(\to\) 相关树上算法;
强连通分量 \(\to\) DAG \(\to\) DAG 上动态规划。
一个点唯一存在于一个边双或者强连通分量,但可能存在于多个点双连通分量。
圆方树:https://www.cnblogs.com/xsl19/p/13513635.html。
例题 5:「JSOI2012」越狱老虎桥
边双里的边肯定不能割,那么可以把边双缩点,问题就放到了树上。
因此按边权从小到大标记树边,直到已标记的树边无法被一条链覆盖为止。无法被覆盖的最后一条边就是答案。
具体的,有一种简洁做法,直接按照边权最小的边的一个端点为根建树,记录 dfs 序和子树大小 ,每次记录链的左右端点,看能不能向下延伸。如果不能就直接输出答案。
代码链接:https://paste.ubuntu.com/p/WYJTx2Gpn9/。
例题 6:「CEOI2017」One-Way Streets
求边双,一个边双连通分量内的所有边的方向都是可任选的。
将边双缩点后变成一棵树,问题就变成从树上一点 \(x\) 能到另一点 \(y\),可以树上差分的思想处理,也可以并查集直接跳,每次将这个点和父亲缩起来。
注意求边双要记录每个点属于哪一个边双可以 Tarjan 的时候直接拿栈求出来。
Tarjan 代码:
void Tarjan(int u, int lst)
{
dfn[u] = low[u] = ++tim, in_stk[stk[++tp] = u] = 1;
for (int i = head[u]; i; i = nxt[i])
{
int v = ver[i];
if ((i >> 1) == lst) continue;
if (!dfn[v])
{
Tarjan(v, i >> 1);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u])
{
++cnt; int y = -1;
do in_stk[y = stk[tp--]] = 0, bel[y] = cnt; while (y ^ v);
}
}
else if (in_stk[v]) low[u] = min(low[u], dfn[v]);
}
if (!lst)
{
++cnt;
while (tp) in_stk[stk[tp]] = 0, bel[stk[tp--]] = cnt;
}
}
完整代码链接:https://loj.ac/s/1383823。
2-SAT
对于 \(n\) 个布尔变量,给出若干形如「\(x_i\) 为 true / false 或 \(x_j\) 为 true / false」的条件,目标是给每个变量赋值使得所有条件得到满足。
这时候可以连边:\(\lnot x_i\to x_j\) 和 \(\lnot x_j\to x_i\),即当 \(x_i\) 取不到要求的值的时候 \(x_j\) 一定要取到,反之亦然。
如果一个变量两种取值的点在同一个强连通分量里说明矛盾,否则取拓扑序较大的那个强连通分量内的值。
值得注意的是,Tarjan 求出的标号是拓扑序倒序。因此可以直接判。
例题 7:「UOJ210」寻找罪犯
\(crime_i\) 表示 \(i\) 号嫌疑人是不是罪犯,\(fake_{i,j}\) 表示 \(i\) 号嫌疑人说的前 \(j\) 句话里有没有假话。设第 \(i\) 个人说的第 \(j\) 句话是“\(x_{i,j}\) 不是罪犯”:
\(\varphi = \land_{i,j}(crime_{x_{i,j}} \Rightarrow crime_i) \land (crime_{x_{i,j}} \Rightarrow fake_{i,j})\land(crime_{x_{i,j}} \Rightarrow \lnot fake_{i,j-1}) \land (fake_{i,j-1}\Rightarrow fake_{i,j})\)
如果说的是“是罪犯”就用 \(\lnot crime_{x_{i,j}}\) 替换 \(crime_{x_{i,j}}\)。
代码链接:https://uoj.ac/submission/535827。
三元环计数
对一张无向图数有多少无序三元组 \((x,y,z)\) 满足图中存在边 \((x,y),(x,z)\) 以及 \((y,z)\)。
对所有点按度数为第一关键字、标号为第二关键字排序,把所有边按照排序得到的排名重定向(排在前面的指向排在后面的),这样可以保证新生成的有向图是一个 DAG,且每个点的出度不超过 \(\sqrt m\)。
枚举边 \((A,B)\),先遍历 \(A\) 的所有出边并标记,再遍历 \(B\) 的所有出边,发现一个标记就说明找到了一个三元环。
总时间复杂度是 \(\mathcal{O}(m\sqrt m)\),同时也证明了 \(m\) 条边的无向图中的三元环数量是 \(\mathcal{O}(m\sqrt m)\)。
例题 8:「POI2013」CEN-Price List
最短路有 \(3\) 中情况:
- 走原图的边,即只走 \(a\) 边。
- 将两条 \(a\) 边并做一条 \(b\) 边走,最后根据奇偶性可能剩下一条 \(a\) 边。
- 只走 \(b\) 边。
前两种情况可以直接在原图上 BFS 得到,考虑第三种:当 \(u\) 点向外转移时,需要先枚举一个 \(u\) 的相邻点 \(v\),再枚举一个 \(v\) 的相邻点 \(w\),若 \(u\),\(w\) 之间没有边,便可以从 \(u\) 来更新 \(w\)。
看到这种枚举两次出边的就要想到三元环计数。
注意这个过程中每个点只会被更新一次,所以一旦 \(u\) 成功更新 \(w\)(等价于 \(u\),\(w\) 之间没有边),\(v \to w\) 这条枚举就可以删了。也即对于成功更新的部分,复杂度是关于一阶段枚举量线性的,也即 \(\mathcal{O}(m)\)。
问题在于不能成功更新的部分,即 \(u\),\(w\) 之间有边,这说明 \((u,v,w)\) 构成一个三元环,而三元环个数是 \(\mathcal{O}(m\sqrt m)\) 的,每个三元环对枚举复杂度的代价又是常数,因此总时间复杂度为 \(\mathcal{O}(m\sqrt m)\)。
代码链接:https://paste.ubuntu.com/p/MGs3BQkDJr/。
欧拉回路
分无向图/有向图两种版本:无向图存在欧拉回路要求所有点的度数均为偶数;有向图则要求所有点入度等于出度。
欧拉路不要求回到起点,要求除起点终点外其他点都是偶度点。可以理解为新加一条边连接起点终点后的欧拉回路。
多笔画(多条欧拉路)问题?奇度点两两匹配后求出欧拉回路,把新加的边删除后即得到了多条欧拉路。
例题 9:「UOJ117」欧拉回路
欧拉回路模板,注意要加上当前弧优化。
代码链接:https://uoj.ac/submission/535486。
例题 10:「CF429E」Points and Segments
记黑色区间的权值为 \(1\),白色为 \(-1\),等价于要求任意一个点被覆盖的所有区间的总权值 \(\in [-1,1]\)。
对于区间 \([l,r]\),从 \(l\) 向 \(r + \epsilon\) 连一条边,并对所有奇度点按横坐标顺序两两连边(相当于加入了一些新区间),在新生成的图上求出欧拉回路,把从左往右的边染成黑色,从右往左的边染成白色,此时所有点被覆盖区间的总权值均为 0。
注意到新加入的区间集是 无交 的,因此只要直接去掉这些区间,便可以满足原题中权值 \(\in [-1,1]\) 的要求。
代码链接:https://codeforces.ml/contest/429/submission/146790229。
最小斯坦纳树
解决找到一张图上使 \(K\) 个点连通的最小连通块的边权 / 点权和的问题。
容易发现肯定答案是一棵树。
设 \(f_{i,S}\) 为以 \(i\) 为根,包含集合 \(S\) 内的点的最小代价。
转移的话,枚举 \(i\) 的儿子数量,可以有:
\(f_{i,S}\leftarrow f_{j,S}+w(i,j)\),\(f_{i,S}\leftarrow f_{j,T}+f_{i,S-T}(T⊆S)\)。
按照 \(S\) 升序枚举转移即可。
第一种转移可以用最短路,第二种直接枚举子集。
例题 11:「洛谷 P6192」【模板】最小斯坦纳树
模板题。
代码链接:https://paste.ubuntu.com/p/rJZjGvVpZ3/。
例题 12:「WC2008」游览计划
边权改成了点权。
输出方案直接在转移的时候记录前驱节点即可。
没了。

浙公网安备 33010602011771号