图上联通性
Tarjan
强连通分量
找到有向图强连通分量,也就是对于每个点找到和它构成环的所有点。
我们只要在 dfs 的过程中维护一个栈 \(s\),保存的是 \(x\) 的祖先节点 \(anc(x)\) 和那些已经访问过的存在一条路径到 \(anc(x)\) 的节点。这个第二类的点的由来就是假设目前在 \(x\) 点进行统计,那么栈里面除了它的祖先还有它子树内一些连上来的边的相关点。这些点都是在一个强连通分量中,所以我们不能在每个儿子回溯之后立即统计,而是应该等到所有儿子都 dfs 完之后再统计。如果 \(x\) 的一个子树没有返祖边那么意味这个子树内部也有强连通分量,其中的点会被弹出栈也就不会被算在 \(x\) 中了。
对于 \(v \in s\),如果存在一条边 \(u\to v\),那么就代表 \(u\) 和 \(v\) 在同一个环中。
定义 \(low_x\) 表示 \(x\) 的追溯值,\(low_x=\min\limits_y\{dfn_y\}\),其中 \(y\) 满足在 \(x\) 子树中或者存在一条从 \(x\) 子树内出发的边可以抵达 \(y\)。
我们执行以下流程:注意以下 \(\gets\) 符号基本为取 \(\min\)
- 当 \(x\) 被第一次访问的时候,将 \(x\) 入栈,并 \(low_x \gets dfn_x\)。
- 扫描 \(x\) 所有出边 \((x,y)\),如果 \(y\) 没有被访问过,那么继续搜索 \(y\),并且 \(low_x\gets low_y\);如果 \(y\) 被访问过且 \(y\) 在栈中那么 \(low_x\gets dfn_y\)。
- 从 \(x\) 回溯之前,判断是否有 \(low_x=dfn_x\) 成立,若成立,就不断从栈中弹出节点,直到 \(x\) 出栈。
在以上过程中,若从 \(x\) 回溯前有 \(low_x=dfn_x\) 成立,那么栈中从 \(x\) 到栈顶的所有节点构成一个强联通分量。注意这个和之前的不同点就是需要相等的时候才统计。
同时小心如果被访问过也不能直接用 \(dfn_v\) 来更新 \(low_u\),因为有向图的 dfs 树除了返祖边还有横叉边,如果兄弟子树内已经构成了一个强连通分量就不能拿出来更新它了。
缩点之后,我们得到的是有向无环图。
P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G
我们对于建立反边,题目就转化为有多少点可以到达全局所有点。
可以先缩点一下,然后变成了一个 DAG,我们直接 bfs 即可。
P3387 【模板】缩点
缩点之后 topo 排序过程中 dp 即可。
在 DAG 上有,\(dp_v\gets dp_u+val_v\)。
点双
和求解强联通分量类似的方法求出 \(low\) 和 \(dfn\) 数组,注意当某个点已经被访问过了,我们不需要依据其是否在栈中再选择更新 \(low\) 数组,因为无向图的 dfs 树只有树边和返祖边。
条件是 \(low_v\ge dfn_u\)。
几个小细节,一是特判独立点,二是特判根节点度数为 \(1\) 的时候不能成为割点。
下面是一些结论,
\(x,y\) 的必经点是 \(z\),那么 \(z\) 是割点。
\(x\) 和 \(y,z\) 分别双联通,且 \(y,z\) 不双联通的时候,\(x\) 为 \(y-z\) 的必经点。
在同一个环内的边在同一个点双内,而非边双,因为同一边双内部可能会有两个环在一个点处相交的情况,这样子这两个环依然在同一边双内。想要求出一个点双内的边数不能直接访问边双内部所有点的所有出边,因为一个点可能在多个点双内出现,这个点出度比较大的时候复杂度会不正确。
正确做法是枚举边,通过两个端点来贡献给两点所在的公共点双,其中它们每个点可能在多个点双内,我们还是无法快速求解。于是直接建立广义圆方树,再 dfs 一下记录父亲。然后对于 \((u,v)\),如果 \(fa_{fa_u}=v\),就贡献给 \(fa_u\),如果 \(fa_{fa_v}=u\) 就贡献给 \(fa_v\),否则 \(fa_u=fa_v\),随便选一个贡献就行了。这可以保证贡献给两个点所连接的公共方点上面。
边双
和求解强联通分量类似的方法求出 \(low\) 和 \(dfn\) 数组,注意当某个点已经被访问过了,我们不需要依据其是否在栈中再选择更新 \(low\) 数组,因为无向图的 dfs 树只有树边和返祖边。
条件是 \(low_v>dfn_u\)。区分一下能不能取等,边双的选择对象不是 \(u-fa\) 这条边而是在 \(v-u\) 这条边上所以不能跨越这条边故为小于号。
注意一下记录一下入边,不能从入边转移。
QOJ4809. Maximum Range
给定一张无向图,要求构造一个边不重复的环,使得上面的边权极差最大。\(n\le 10^5\)。
注意环的条件是边不重复,点可重复。于是我们求一遍边双,在每个边双联通分量之中任意选择两条边都可以构造出一个环,直接选取最大边和最小边即可。
构造方案很有意思,对于两条边直接插入两个虚点然后对于单位权图跑一遍网络流,当流量达到 \(2\) 的时候立刻停止扩展,对于所有流过的边抽出来跑一遍欧拉路径就行了。时间复杂度 \(O(n)\)。
广义圆方树
对于仙人掌可以用圆方树求解,对于一般图则是用广义圆方树。
就是对于原图进行点双计算,每当找到一个点双,我们就建立一个方点,然后将点双中的点都当做圆点,和这个方点连边,有点菊花图的感觉。
有个问题就是如果对于点双内所有点都这么连边,按理来说这些点都是对称的,那么如何突出割点的地位?其实关键在于一个点可以作为很多个点双的割点,或者被包含于不同点双,所以我们在弹出当前点双内的点的时候,其实还保留了割点在栈中,这样子割点就会再被包含于其他点双,从而与其他方点连边,这也从另一个方面证明了圆方树中圆方交替出现的事实。同时也证明了圆点 \(x\) 的度数等于包含他的点双的数量。也证明了原图中的某点是割点当且仅当它在圆方树中是叶子节点。所以同时要注意 tarjan 是要直接弹出 \(u\),而圆方树弹出 \(u\) 的儿子 \(v\) 之后就要停止。
圆方树的作用就就是很好地刻画了两点之间的联通性,对于圆方树上两点他们路径上的点就是原图上的必经点。圆方树上删除某点后的连通性和原图上删除该点的联通相同。
注意建立圆方树的时候没有像点双那样子有根节点特判。
对于边双相关,我们直接缩点之后就是一颗树了,对于点双相关缩点之后还是图所以我们可以采用圆方树来处理。
小技巧以及结论
对于一条原来的边 \((u,v)\),如何定位?由于圆方树是方圆相间,所以他们直接隔着一个方点,这个点就是我们所找,于是直接找到 \(u,v\) 中深度较深点的父亲即可。
P4630 [APIO2018] 铁人两项
巧妙赋值,圆点赋 \(-1\),方点赋值为点双大小。然后两点之间的答案就是路径和。
注意最后统计答案,思考角度应该是以某个点为基础进行贡献法,而不是合并子树法粗暴统计,那样子太难写了。
仙人掌(圆方树)
分为点仙人掌和边仙人掌,分别代表每个点/每条边都属于至多一个简单环之中。其中所有的点仙人掌都是边仙人掌。
对于边仙人掌常用的处理手段是建立圆方树。对于点仙人掌可以简单点,我们求一遍点双就行了,每个点双的内部都是一个环或者孤立点。
我们对于每个环都建立一个方点,环上的所有点作为圆点呈菊花状连向方点。和广义圆方树的区别,就是广义圆方树是圆方相间,而圆方树则是圆圆之间可以连边的,方方之间无边。
这里用 tarjan 边双来求圆方树,问题是一个边双内可能有多个简单环,所以需要一些方法处理。
显然每个边双都有一个 "根" 节点,也就是那个 \(low_v> dfn_u\) 的点 \(v\),注意不是 \(u\),这个时候首先要连上两个圆点之间的边 \((u,v,w)\)。
一般求边双,就是不经过割边而将联通块内的所有点都加入边双,不过这里需要处理多个环的问题,我们记录进入边,从 \(u\) 点开始寻找以该点为根的边双,我们找其儿子 \(v\),满足 \(dfn_v>dfn_u\) 且进入边不是 \((u,v)\),进行回溯,直到再次回到 \(u\) 点,我们就找到了一个环。
找到环之后就可以建立圆方树了,新建一个方点,把所有环上点连上去即可。
P5236 【模板】静态仙人掌
设计边权的问题,我们需要对于圆方树上的边设计边权。按照上文中的方法找到环之后,将方点记为 \(c\),对于边 \((u,c)\) 其权值为 \(0\),对于其余环上点 \(x\),\((c,x)\) 其边权为 \((u,x)\) 之间的最短距离,由于是环上行走最短距离所以就是正反两种距离取 \(\min\)。
对于 \((u,v)\) 距离就是先求一个在圆方树上的 \(\rm LCA\),如果该点为圆点那就直接是树上两点距离。如果是方点需要找到环上的两个进入点,然后顺反距离取 \(\min\)。进入点就是倍增 lca 的过程中最后两个点。
耳分解
对于图 \(G=(V,E)\),还有集合 \(S\),定义耳为一个顶点序列 \(a_1,a_2...a_m\),其中 \(a_1,a_m \in S\),\(a_2..a_{m-1} \in V-S\),且 \(a_i\) 和 \(a_{i+1}\) 之间均有连边。
耳分解就是将图的生成过程写为 \(G_0,G_1...G_t\),其中 \(G_0\) 为一个环,\(G=G_t\),\(G_{i}\) 去掉 \(G_{i-1}\) 就是一个耳。
有向图强联通等价于有向图存在耳分解
无向图双联通等价于无向图存在耳分解。
QOJ3301. Economic One-way Roads
需要找到一个刻画强联通图的方式,这里用耳分解,可以从一个点出发不断通过加一个环的一部分(起点和终点都在已加入的集合里)的方式来得到一个强联通分量。
我们设 \(f_s\) 表示能否通过耳分解得到 \(S\),转移的时候枚举另外一个集合 \(T\) 表示加入集合,这个复杂度太高了。我们改一下,设 \(g_{s,u,v}\) 表示在 \(S\) 集合时,目前的耳构造到了 \(u\),我们的终点是 \(v\) 的最小代价。
枚举集合 \(S\),首先先用 \(g_{s,i,j}+G_{i,j}\) 更新答案 \(f_s\),其中必须满足 \(i,j \in S\) 表示已经构造到最后一步了然后用边 \((i,j)\) 连接。然后需要注意一个点,就是我们求出 \(f_s\) 之后,应该用 \(f_s\) 反向更新 \(g_{s,i,j}\)。然后再用 \(g_{s,i,j}\) 向外扩展,枚举 \(u\),\(g_{s\cup{u},u,j} \gets g_{s,i,j}+G_{i,u}\)。还有一个细节就是如果 \(i=j\),我们要是只往外扩展一个点,可能会出现更新 \(g\) 时用 \(i\to u\),然后更新答案的时候又用 \(u\to i\),这就代表一条边被正反使用了,不符合要求,于是当 \(i=j\) 的时候,我们应该寻找两个不同 \(u\) \(v\) 强制将两个端点转到 \(u\) \(v\) 这样就行了,时间复杂度 \(O(2^nn^3)\)。
注意细节由于 dp 的时候是只加入对于强连通分量有贡献的边,但是这并不代表其他边不要定向,所以我们应该提前按照费用小的那边先给所有边定向,然后记录一下费用大的边翻转的费用。

浙公网安备 33010602011771号