Live2D

Note -「圆方树」学习笔记

圆方树的定义

  圆方树是由一个无向图转化出的树形结构。转化方法为:

  • 所有原图的点为“圆点”。
  • 对于每个点双连通分量:
    • 删去点双内部“圆点”间的连边。
    • 新建点双的代表点——“方点”。
    • 方点向点双内的所有点连边。

  举个例子:

UbA9Et.png

  观察图片,我们可以得到圆方树的一些性质:

  • 不存在相邻的方点。
  • 一个圆点同时隶属于它所邻接的所有方点所代表的点双。可见所有非叶子圆点都是原图的割点。
  • 从圆点 u 到圆点 v 的树上路径所经过的圆点是原图 uv 的所有路径一定经过的点。

圆方树的构造

  既然跟点双相关,那肯定是 Tarjan 老爷爷上场啦!

  圆方树的构造算法实质上就是在求点双的基础上构建一个新图。设当前结点为 u,若发现其儿子(DFS 树上儿子)vlow[v]>=dfn[u],那么就知道 u 是一个割点或者是 DFS 树根。我们不必对此加以区分,直接将 u 和新建的方点 w 连边,然后 w 向所有退栈的点连边,圆方树就构造完成了。

实现

inline void Tarjan ( const int u, const int f ) {
	dfn[u] = low[u] = ++ dfc, stk[++ tp] = u;
	adj ( src, u, v ) if ( v ^ f ) { // 枚举原图上u的临界点,并保证其不是DFS树上u的父亲。
		if ( ! dfn[v] ) {
			Tarjan ( v, u ), chkmin ( low[u], low[v] );
			if ( low[v] >= dfn[u] ) {
				tre.add ( u, ++ snode ); // snode初值为n,用于计数新建结点。
				do tre.add ( snode, stk[tp] ); while ( stk[tp --] ^ v );
                                // 向每个退栈的点连边。
			}
		} else chkmin ( low[u], dfn[v] );
	}
}

细节

  在运用圆方树的过程中,我们往往需要在方点维护一些信息。为了避免重复,我们一般在割点处单独维护圆点信息,而在方点中不考虑割点。(即在上文算法中,结点 snode 不考虑 u 的信息,只记录 v 的信息。)

圆方树的运用

「BZOJ 3331」压力

Description

  Link.

  给定一个 n 个点 m 条边的连通无向图,并给出 q 个点对 (u,v),令 uv 的路径所必经的结点权值 +1。求最终每个结点的权值。

  n105m,q2×105

Solution

  看到”必经之点“,应该考虑圆方树。

  对于每个点对,直接在圆方树上作差分,最后一遍 DFS 求每个圆点的子树和即可。

  详细题解:my solution

「洛谷 P4320」道路相遇

Description

  Link.

  给定一个 n 个点 m 条边的连通无向图,并给出 q 个点对 (u,v),询问 uv 的路径所必经的结点个数。

  n,q5×105qmin{n(n1)2,106}

Solution

  和上一道几乎一样。预处理圆方树上每个点到根经过的圆点个数,然后求 LCA 计算答案。

  详细题解:my solution

「APIO 2018」「洛谷 P4630」铁人两项

Description

  Link.

  给定一个 n 个点 m 条边的无向图(不保证联通),求有序三元点对 (s,c,f) 的个数,满足 s,c,f 互不相同,且存在一条从 sc 再到 f 的简单路径。

  n105m2×105

Solution

  首先考虑这样一个问题,若 s,c,f 在同一点双中,是否一定满足条件。

  答案是肯定的,这里不细说。

  那么如果固定 s,f,合法的 c 就可以在圆方树 sf 路径上的所有圆点和所有方点所代表的圆点(除去 s,f)。这是因为 c 取在任意点双内部,由我们的结论,都一定可以从某点进入点双,经过 c,再从某点走出点双。

  但简单的计算会导致重复——一个圆点对多个方点有贡献。

  举个例子,对于 uwvw 是圆点,u,v 是方点,如果我们单纯地用点双大小作为方点的权值,w 就会在 uv 中分别计算一次。

  解决办法很巧妙:将圆点的权值设为 1。考虑 sf 的路径必然是”圆-方-圆-……-圆-方-圆“,两个端点的 1,去除了 sf 的贡献,中间的 1 去除了在左右的”方“中重复的贡献。那么,合法的 c 的数量就是 sf 的树上路径权值之和。

  于是,相当于求树上点对的路径权值和。反过来,固定 c,维护子树信息求出 (s,f) 的方案数,计算 c 的贡献即可。

  详细题解:my solution(带上文所述性质的证明)。

「CF 487E」Tourists

Description

  Link.

  维护一个 n 个点 m 条边的简单无向连通图,点有点权。q 次操作:

  • 修改单点点权。
  • 询问两点所有可能路径上点权的最小值。

  n,m,q105

Solution

  怎么可能维护图嘛,肯定是维护圆方树咯!

  一个比较 naive 的想法是,每个方点维护其邻接圆点的最小值,树链剖分处理询问。

  不过修改的复杂度会由于菊花退化:修改”花蕊“的圆点,四周 O(n) 个方点的信息都需要修改。

  联想到 array 这道题,我们尝试”弱化“方点所维护的信息。每个方点,维护其圆方树上儿子们的点权最小值。那么每次修改圆点,至多就只有其父亲需要修改信息了。

  于是,每个方点用 std::multiset 或者常见的双堆 trick 维护最小值信息(推荐后者,常数较小),再用一样的树剖处理询问即可。

  详细题解:my solution

「SDOI 2018」「洛谷 P4606」战略游戏

Description

  Link.

  给定一个 n 个点 m 条边的无向连通图,q 次询问,每次给出一个点集 s,求至少在原图中删去多少个点,使得 s 中存在两点不连通。多组数据。

  每组数据 n,q105m,|s|2×105

Solution

  看到 |s| 的限制,不难联想到虚树或者其它与 DFN 相关的算法。

  所以,先建出圆方树,并处理好 DFN,LCA 的一系列信息。考虑到答案就是 s 中的点在树上构成的连通块中不在 s 集合里的圆点个数,可以求一个树上前缀和:sumu 表示从 u 到根路径上的圆点个数。处理询问时,先将 s 按 DFN 从小到大排序,两两 LCA 计算贡献(s1s|s| 也要算一次),最后除以 2,得到 cnt。不过整个连通块的根(LCA(s1,s|s|))这个点的贡献被遗漏了,所以要再加上这个点单独的贡献。最终答案就是 cnt|s|

  详细题解:my solution

「BZOJ 4316」小C的独立集

Description

  Link.

  求包含 n 个结点 m 条边的仙人掌的最大独立集。

  n5×104m6×104

Solution

  建出圆方树,考虑树上 DP。

  设状态 f(i,0/1) 表示该点不选择/不限制选择与父亲相邻的圆点(对于圆点,即它本身)时,子树内的最大独立集。转移分圆点和方点讨论:

  • 圆点:很显然,f(u,0)=vsonuf(v,1),f(u,1)=max{f(u,0),vsonuf(v,0)+1}

  • 方点:考虑把环展开成链。在链上做一个子 DP,这里不赘述。

  最后,f(root,1) 就是答案。

  详细题解:my solution(含子 DP 详细说明)。

「洛谷 P5236」「模板」静态仙人掌

Description

  Link.

  给定一个 n 个点 m 条边的仙人掌,q 组询问两点最短路。

  n,q104m2×104

Solution

  提出一个环来考虑,从环上一点 uv,无非两条路径。可以按顺序处理一个前缀和,计算两条路径边权的最小值。这里不细说。

  建圆方树(很多题解说建树的细节与普通图不一样,其实正常建也没有任何问题 qwq),发现一个圆点走进方点(点双),在走到父亲圆点的最短距离可以在 Tarjan 算法中预处理出来。考虑圆方树上的边权:圆点到父亲方点的边权为上述最短距离,否则为 0。对于询问 (u,v),找到其 LCA(设为 w),分 w 的情况讨论:

  • w 是圆点,那么 LCA 时求出的树上距离就是答案。
  • w 是方点,此时树上距离实际上是 uv 走到 w 的父亲圆点的距离之和,明显时错误的——uv 走进同一个点双后,需要求一次最短距离而非直接在父亲会和。所以可以倍增求到 u 走进点双的第一个点(即方点向 u 点方向的儿子圆点)u,同理求出 v,利用处理的前缀和求到 uv 的最短距离。那么答案就是 dist(u,u)+dist(u,v)+dist(v,v)

  详细题解:my solution(含带图的环上前缀和讲解)。

「HNOI 2009」「洛谷 P4410」无归岛

Description

  Link.

  原题的题目描述愣是没看懂 qwq。

  给一个 n 个点 m 条边的仙人掌(或许有特殊性质,不过这里的解法不需要 www),点有点权,求带权最大独立集。

  n105m2×105

Solution

  和上上题几乎一样,把圆点的 +1 改成 +max{0,au} 就好。

  我绝对不会告诉你我快读没判负数挂了三次 qwq。

  详细题解:看上上题的吧,再水一篇太过分了 www。

posted @   Rainybunny  阅读(403)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示