基环树
基环树
一、基本概念
如果一张无向连通图包含恰好一个环,则称它是一棵基环树(Pseudotree)。
如果一张有向弱连通图每个点的入度都为 ,则称它是一棵 基环外向树。
如果一张有向弱连通图每个点的出度都为 ,则称它是一棵 基环内向树。
多棵树可以组成一个森林(Forest) ,多棵基环树可以组成基环森林(Pseudoforest) ,多棵基环外向树可以组成基环外向树森林,多棵基环内向树可以组成基环内向森林(Functional graph) 。
二、算法实现
在基环树中,有许多独一无二的性质,这也就成为良心出题人增加题目难度的一种措施。通常会结合树的直径进行考察。
下面就一些例题进行分析
下面以 [IOI2008] Island 举例。
题目大意:现有一个基环森林,求出森林中每颗基环树的直径的和。
基环树直径,就是图中最长链的长度。因为基环树的一些特性,这也成为常考点。 显然,基环树的最长链可能有两种情况:
在去掉“环”的某棵子树中。
经过“环”,其两端分别在去掉“环”上所有边之后的两颗不同子树中。
我们可以先用一次dfs找出基环树中的“环”,把“环”上的节点做标记,并用数组
ring[]
记录。设环上的节点为 \(s_1,s_2,\cdots,s_t\)。
1 | bool dfs(int x,int last){//用bool数组进行记录:true表示在环上;false表示不在环上 |
从每个 \(s_i\) 出发,在不经过环上其他节点的前提下,再次执行dfs,即可访问去掉“环”之后以 \(s_i\) 为根的子树。在这样的每棵子树中,按照求树的直径的方法进行树形DP并更新答案,即可处理第一种情况。同时,还可以计算出 \(d[s_i]\),表示从节点 \(s_i\) 出发走向以 \(s_i\) 为根的子树,能够到达的最远节点距离。
1 | void tree_dp(int x){//树形DP |
最后,考虑第二种情况。这相当于找到环上两个不同的节点 \(s_i,s_j\),使得 \(d[s_i]+d[s_j]+dist(s_i,s_j)\) 最大。其中,\(dist(s_i,s_j)\) 表示 \(s_i,s_j\) 在环上的距离,有顺时针、逆时针两种走法,取较长的一种。可以将环断开成链再复制一倍,再用单调队列解决。(还可以用前缀和的方法快速求出,下面会提及)。
1 | ll slove(int x){ |
如果需要,可以打开完整的代码