基环树 dp 入门

基环树

基环树,也被叫做环套树,是一种有 \(n\) 个点和 \(n\) 条边的特殊的图,因为其有 \(n\) 条边而树有 \(n-1\) 条边,所以它的外形看起来就是一个其中有一个环的树,下图展示的就是一个基环树:

基环树的问题很多时候都是树形 dp,对于基环树我们常见的处理方式是 拆边成树+按环求值。

我们将环上的一条边拆掉,然后将环上一点看成树根将其变成树或者把环上每个点的子树拎出来进行求解,比如上面的基环树我们可以这么变化:

内向基环树

每个点有且仅有一条出边的基环树是内向基环树,从子树开始边一次向上相连,我们发现每一个不在环上的点连的都是它的父亲,那么很明显环上的点就只能互相指了,最后出来的这个外围所有点向内,环上点互指的树就是内向基环树:

外向基环树

外向基环树是同理的,只是边的方向都是反的而已。

基环树找环

对于内向/外向基环树这些特殊的基环树我们有一种暴力的方式去寻找,比方说内向基环树,对于每一个点,我们重复去找他的父亲,这样到最后必然是会找到环上的,我们用标记数组去记录每一个点到了多少次,第一个被找到两次的必然就在环上,外向基环树同理,我们将父亲改成儿子就可以了

另外还有一种比较常见的在无向基环树上找环的方式是利用拓扑排序,我们将这个无向基环树上的每一条边看成是两条方向相反的有向边,然后统计每一个点的入度为 \(cnt_x\) ,很明显的对于基环树的叶子节点,他们的入度为 \(1\),我们它能到的所有的点的入度减上 \(1\),然后在对入度为 \(1\) 的重复这个操作,我们可以发现,环上的每一个点入度至少为 \(1\),再环上的所有的点都不会被消掉,所以最后入度大于 \(1\) 的点就是环上的点。

P8655 发现环

这个题是基环树上找环的板子题,过程给代码进行参考:

void tp(){
	queue<int> q;
	for(int i=1;i<=n;i++){
		if(cnt[i]==1){
			q.push(i);
			vis[i]=1;
		}
	}
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];~i;i=nt[i]){
			int v=ver[i];
			cnt[v]--;
			if(cnt[v]==1){
                vis[v]=1;
				q.push(v);
				//最后1~n里 vis 为 0 的就是答案
			}
		}
	} 
}

基环树 dp 入门问题

P2607 [ZJOI2008] 骑士

看到这个题我们是很容易发现这是一个在基环树上的没有上司的舞会,我们考虑树的环上随便找两个相邻的点 \(u,v\),将其中间的边删掉然后分别进行舞会。

我们再说一下具体怎么去舞会,我们将仇恨关系进行反向建边,定义 \(f_{u,0/1}\) 表示 \(u\) 这个点的子树里 \(u\) 自己不取和取的最大收益,因为仇恨关系,我们得到 \(u\) 不能和它的儿子同取,我们可以写出来式子:

\[f_{u,0}=\sum_{v\in son_u} \max(f_{v,0},f_{v,1}) \]

\[f_{u,1}=\sum_{v\in son_u}f_{v,0} \]

我们思考在有了 \(u,v\) 的答案后怎么去求出最终的答案,我们可以进行分类讨论:

  1. \(u,v\) 都不取
  2. \(u\) 取 \(v\) 不取
  3. \(v\) 取 \(u\) 不取

我们可以发现在这个有向的图里肯定要么是 \(f_{v,1}\) 包括在 \(f_{u,0}\) 里,要么是 \(f_{u,1}\) 包括在 \(f_{v,0}\) 里,最后的答案很明显不能是个和,而是 \(\max(f_{u,0},f_{v,0})\)。

原题中并没有保证构成的刚好是一颗基环树,实际是一个基环树森林,我们不能只看一个基环树。

P4381 [IOI 2008] Island

题目中给了我们一个基环树森林,我们要做的是找出这里面的所有基环树的直径的和(我们在这里认为一个基环树的直径是最长的简单路径的长度)。

对于一颗基环树,它的直径有两种情况:

  1. 这个路径不经过环(也就是两段点都在环上一点的子树里)
  2. 这个路径经过环

对于第一种情况,很好处理,我们按照朴素的求直径的方式求就可以了,这里不再过多地去赘述。

对于第二种情况,我们可以枚举环上的两点 \(u,v\),最后的答案就是 \(u,v\) 两颗子树的最大深度(我们后面定为 \(f_u,f_v\))加上 \(u,v\) 两点在环上的距离大小(后面我们定它为 \(d_{u,v}\)),求这个我们考虑去拆环为链,在这个链上求解问题时我们发现 \(d_{u,v}=s_u-s_v\)(\(u\) 在 \(v\) 的后面,\(s_x\) 我们定义为环上边的前缀和),最后的答案就成了 \(f_u+s_u+f_v-s_v\),我们发现当 \(f_v-s_v\ge f_w-s_w\) (\(v\) 在 \(w\) 后面)的时候 \(w\) 就不可能再去更新后面的答案了,我们就要及时把它扔掉,我们用单调队列去做。

P1399 [NOI2013] 快餐店

这个题的答案我们可以简化为我们取任意两点 \(i,j\) 它们间的最短距离的最大值除以二,对于这个最短距离,他会有一个路径,这个路径分为两种情况:

  1. 不经过这个环
  2. 经过这个环

对于不经过这个环的情况,答案就在环上每一个点的子树里面,我们可以在子树上 DFS,对于每一个点,找出以它为子树的点里距离最大的次大的点的距离,答案就是他们的和的最大值。

在这样的路径经过这个环的时候,我们可以把基环树缩成这样的样子:

每一个环上的点的子树只剩下离它最远的点即可,很明显这个时候我们可以直接用 \(d_i\) 来表示,那么答案就是 \(\min(d_i+d_j+sum_{i,j},d_i+d_j+\sum s-sum_{i,j})\)。那么这些东西我们就可以进行一个预处理,然后就做完了(

posted @ 2026-05-16 08:16  Loveyhb  阅读(71)  评论(0)    收藏  举报