Loading

【笔记】二次换根

该算法可以两次 DFS 求出以每个节点为根所得树的深度。

算法实现

朴素做法:枚举每个节点作根的情况进行 DFS,\(O(n^2)\)

要想优化,就必须避免改变树的结构。我们发现,子树 \(u\) 的节点在以 \(u\) 为根的大树中到根 \(u\) 的距离都减少了一,其他节点因为要经过 \(u\) 的父节点到达 \(u\),所以距离都加了一。

于是我们使用树形 DP 求出递推式。有两种写法:

1. 统计以 \(u\) 为根子树外的节点

第一次 DFS,求出以每个节点为根的子树到这个节点的深度和 \(down\)

第二次 DFS,可以发现以 \(u\) 为根构造的树的深度和为 \(down_u\) 加上以 \(u\) 为根子树外的节点深度和 \(up_u\)。我们使用树形 DP 求出 \(up_u\) 和以 \(u\) 为根的子树节点树 \(c_u\),最后统计 \(down_u+up_u\) 的最大值。

\(up_u\) 的递推式可以这么求:\(u\) 父亲节点的 \(up\) \(+\) 父亲节点的 \(down\)(即以父亲为根树的深度总和)\(-\) \(u\) 节点的 \(down\) (删去 \(u\) 子树)\(-\) \(c_u\)\(u\) 节点子树的节点深度全部减一)\(+\) \(n-c_u\)\(u\) 节点子树以外的节点深度全部加一)

void dfs(int root,int fa){
	c[root]=1;
	dep[root]=dep[fa]+1;
	for (int i=h[root];i;i=e[i].nxt) {
		int v=e[i].to;
		if (v!=fa) {
			dfs(v,root);
			c[root]+=c[v];
			in[root]+=in[v]+c[v];
		}
	}
}
void dp(int root,int fa){
	for(int i=h[root];i;i=e[i].nxt){
		int v=e[i].to;
		if(v!=fa){
			f[v]=f[root]+in[root]-in[v]-c[v]+n-c[v];
			dp(v,root);
		}
	}
}

2. 统计以 \(u\) 为根的深度总和

第一次 DFS 只维护 \(c_u\) 即可。

第二次 DFS 求出以 \(u\) 为根的深度总和,即 \(u\) 子树集体深度减一、非 \(u\) 子树集体深度加一。\(dp_v=dp_u-c_v+n-c_v\)

void dfs(int root,int fa){
	c[root]=1;
	dep[root]=dep[fa]+1;
	for (int i=h[root];i;i=e[i].nxt) {
		int v=e[i].to;
		if (v!=fa) {
			dfs(v,root);
			c[root]+=c[v];
		}
	}
}
void dp(int root,int fa){
	for(int i=h[root];i;i=e[i].nxt){
		int v=e[i].to;
		if(v!=fa){
			f[v]=f[root]-c[v]+n-c[v];
			dp(v,root);
		}
	}
}
posted @ 2025-12-12 23:02  Seqfrel  阅读(0)  评论(0)    收藏  举报