「专题落实」图论 - 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」游览计划

题面

边权改成了点权。

输出方案直接在转移的时候记录前驱节点即可。

没了。

代码链接:https://paste.ubuntu.com/p/7VNftXc8dz/

posted @ 2022-02-20 11:52  csxsi  阅读(11)  评论(0)    收藏  举报