Note -「圆方树」学习笔记
圆方树的定义
圆方树是由一个无向图转化出的树形结构。转化方法为:
- 所有原图的点为“圆点”。
- 对于每个点双连通分量:
- 删去点双内部“圆点”间的连边。
- 新建点双的代表点——“方点”。
- 方点向点双内的所有点连边。
举个例子:
观察图片,我们可以得到圆方树的一些性质:
- 不存在相邻的方点。
- 一个圆点同时隶属于它所邻接的所有方点所代表的点双。可见所有非叶子圆点都是原图的割点。
- 从圆点 到圆点 的树上路径所经过的圆点是原图 到 的所有路径一定经过的点。
圆方树的构造
既然跟点双相关,那肯定是 老爷爷上场啦!
圆方树的构造算法实质上就是在求点双的基础上构建一个新图。设当前结点为 ,若发现其儿子(DFS 树上儿子) 的 low[v]>=dfn[u]
,那么就知道 是一个割点或者是 DFS 树根。我们不必对此加以区分,直接将 和新建的方点 连边,然后 向所有退栈的点连边,圆方树就构造完成了。
实现
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] );
}
}
细节
在运用圆方树的过程中,我们往往需要在方点维护一些信息。为了避免重复,我们一般在割点处单独维护圆点信息,而在方点中不考虑割点。(即在上文算法中,结点 不考虑 的信息,只记录 的信息。)
圆方树的运用
「BZOJ 3331」压力
Link.
给定一个 个点 条边的连通无向图,并给出 个点对 ,令 到 的路径所必经的结点权值 。求最终每个结点的权值。
,。
看到”必经之点“,应该考虑圆方树。
对于每个点对,直接在圆方树上作差分,最后一遍 DFS 求每个圆点的子树和即可。
详细题解:my solution。
「洛谷 P4320」道路相遇
Link.
给定一个 个点 条边的连通无向图,并给出 个点对 ,询问 到 的路径所必经的结点个数。
,。
和上一道几乎一样。预处理圆方树上每个点到根经过的圆点个数,然后求 LCA 计算答案。
详细题解:my solution。
「APIO 2018」「洛谷 P4630」铁人两项
Link.
给定一个 个点 条边的无向图(不保证联通),求有序三元点对 的个数,满足 互不相同,且存在一条从 到 再到 的简单路径。
,。
首先考虑这样一个问题,若 在同一点双中,是否一定满足条件。
答案是肯定的,这里不细说。
那么如果固定 ,合法的 就可以在圆方树 到 路径上的所有圆点和所有方点所代表的圆点(除去 )。这是因为 取在任意点双内部,由我们的结论,都一定可以从某点进入点双,经过 ,再从某点走出点双。
但简单的计算会导致重复——一个圆点对多个方点有贡献。
举个例子,对于 , 是圆点, 是方点,如果我们单纯地用点双大小作为方点的权值, 就会在 和 中分别计算一次。
解决办法很巧妙:将圆点的权值设为 。考虑 到 的路径必然是”圆-方-圆-……-圆-方-圆“,两个端点的 ,去除了 和 的贡献,中间的 去除了在左右的”方“中重复的贡献。那么,合法的 的数量就是 到 的树上路径权值之和。
于是,相当于求树上点对的路径权值和。反过来,固定 ,维护子树信息求出 的方案数,计算 的贡献即可。
详细题解:my solution(带上文所述性质的证明)。
「CF 487E」Tourists
Link.
维护一个 个点 条边的简单无向连通图,点有点权。 次操作:
- 修改单点点权。
- 询问两点所有可能路径上点权的最小值。
。
怎么可能维护图嘛,肯定是维护圆方树咯!
一个比较 naive 的想法是,每个方点维护其邻接圆点的最小值,树链剖分处理询问。
不过修改的复杂度会由于菊花退化:修改”花蕊“的圆点,四周 个方点的信息都需要修改。
联想到 array 这道题,我们尝试”弱化“方点所维护的信息。每个方点,维护其圆方树上儿子们的点权最小值。那么每次修改圆点,至多就只有其父亲需要修改信息了。
于是,每个方点用 std::multiset
或者常见的双堆 trick 维护最小值信息(推荐后者,常数较小),再用一样的树剖处理询问即可。
详细题解:my solution。
「SDOI 2018」「洛谷 P4606」战略游戏
Link.
给定一个 个点 条边的无向连通图, 次询问,每次给出一个点集 ,求至少在原图中删去多少个点,使得 中存在两点不连通。多组数据。
每组数据 ,。
看到 的限制,不难联想到虚树或者其它与 DFN 相关的算法。
所以,先建出圆方树,并处理好 DFN,LCA 的一系列信息。考虑到答案就是 中的点在树上构成的连通块中不在 集合里的圆点个数,可以求一个树上前缀和: 表示从 到根路径上的圆点个数。处理询问时,先将 按 DFN 从小到大排序,两两 LCA 计算贡献( 和 也要算一次),最后除以 ,得到 。不过整个连通块的根()这个点的贡献被遗漏了,所以要再加上这个点单独的贡献。最终答案就是 。
详细题解:my solution。
「BZOJ 4316」小C的独立集
Link.
求包含 个结点 条边的仙人掌的最大独立集。
,。
建出圆方树,考虑树上 DP。
设状态 表示该点不选择/不限制选择与父亲相邻的圆点(对于圆点,即它本身)时,子树内的最大独立集。转移分圆点和方点讨论:
-
圆点:很显然,。
-
方点:考虑把环展开成链。在链上做一个子 DP,这里不赘述。
最后, 就是答案。
详细题解:my solution(含子 DP 详细说明)。
「洛谷 P5236」「模板」静态仙人掌
Link.
给定一个 个点 条边的仙人掌, 组询问两点最短路。
,。
提出一个环来考虑,从环上一点 到 ,无非两条路径。可以按顺序处理一个前缀和,计算两条路径边权的最小值。这里不细说。
建圆方树(很多题解说建树的细节与普通图不一样,其实正常建也没有任何问题 qwq),发现一个圆点走进方点(点双),在走到父亲圆点的最短距离可以在 算法中预处理出来。考虑圆方树上的边权:圆点到父亲方点的边权为上述最短距离,否则为 。对于询问 ,找到其 (设为 ),分 的情况讨论:
- 是圆点,那么 时求出的树上距离就是答案。
- 是方点,此时树上距离实际上是 和 走到 的父亲圆点的距离之和,明显时错误的—— 和 走进同一个点双后,需要求一次最短距离而非直接在父亲会和。所以可以倍增求到 走进点双的第一个点(即方点向 点方向的儿子圆点),同理求出 ,利用处理的前缀和求到 到 的最短距离。那么答案就是 。
详细题解:my solution(含带图的环上前缀和讲解)。
「HNOI 2009」「洛谷 P4410」无归岛
Link.
原题的题目描述愣是没看懂 qwq。
给一个 个点 条边的仙人掌(或许有特殊性质,不过这里的解法不需要 www),点有点权,求带权最大独立集。
,。
和上上题几乎一样,把圆点的 改成 就好。
我绝对不会告诉你我快读没判负数挂了三次 qwq。
详细题解:看上上题的吧,再水一篇太过分了 www。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现