换根DP

换根DP

无根树上的DP?


相关资料

第一个例题距离和
洛谷类似题 - 洛谷 P3478 [POI2008] STA-Station

有一棵 \(n (1 \le n \le 100000)\) 个点的无根树,请求出每个点到其他所有点的距离的和。定义两个点的距离为它们的简单路径上经过了多少条边。

朴素的思想就是枚举每个点作为根遍历整棵树,那么 \(O(n^2)\) 即可解决该问题,但是可以注意到,父节点和子节点各自作为根节点时,数值关系可以传递

定义两个数组 f 和 v:
\(f[i]\) 表示以节点 i 为根的子树中的点到 i 的距离和
\(v[i]\) 表示把节点 i 的父亲 x 作为节点 i 的孩子节点时以 x 为根的子树中的点到 i 的距离和

那么第一次 dfs 时可以维护数组 f 的值,\(f[i] = sz[i] - 1 + \sum f[j]\),j 为 i 的子节点,sz[i]为以 i 为根的子树节点数
第二次 dfs 可以维护数组 v 的值,$ v[t] = v[u] + f[u] - f[t] - sz[t] + n - sz[t]; $,t 为 u 的子节点

未验证的代码

int n, sz[N], f[N], v[N];
vector<int> e[N];

/*
f[i] 表示以节点 i 为根的子树中的点到 i 的距离和
v[i] 表示把节点 i 的父亲 x 作为节点 i 的孩子节点时以 x 为根的子树中的点到 i 的距离和
*/

void dfs1(int u, int fa){
	sz[u] = 1;
	f[u] = 0;
	for(auto v : e[u]){
		if(v == fa) continue;
		dfs1(v, u);
		sz[u] += sz[v];
		f[u] += f[v];
	}
	f[u] += sz[u] - 1;
	return ;
}

void dfs2(int u, int fa){
	for(auto t : e[u]){
		if(t == fa) continue;
		v[t] = v[u] + f[u] - f[t] - sz[t] + n - sz[t];
		dfs2(t, u);
	}
	return ;
}

void solve(){
	cin >> n;
	for(int i = 1; i < n; ++ i){
		int u, v;
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(1, 0);
	dfs2(1, 0);
	for(int i = 1; i <= n; ++ i){
		cout << f[i] + v[i] << '\n';
	}
	return ;
}

第二个例题 流
依旧是考虑以节点 x 为根的子树可以承担的最大流量,以及将节点 x 的父节点作为子节点时可以承担的最大流量,此问题还得考虑边对转移的影响

第三个例题 最长路径
类似于树的直径?


例题

综合运用

模板题

首先需要发现一点的是,对于一个有根树,无论你怎么执行操作,对于一组父子节点 uv,只要你不选择节点 v 进行操作,那么节点 u 和节点 v 的权值就永远不变。所以想要所有节点权值均相等,我们从根开始 dfs 遍历,遇到权值不相等的子节点则选择节点 v 异或 $a[u] \oplus a[v] $即可获得一次答案
不可能对于每个节点都做一次dfs,那么考虑换根DP的做法。可以发现,当根节点从节点 u 传递给子节点 v 的时候,受到影响的答案只有节点 u 和节点 v 之间的关系。原本选择节点 v 进行修改操作,现在改为节点 u 进行修改操作即可,即 \(ans[v] = ans[u] - (a[u] \oplus a[v]) \times sz[v] + (a[u] \oplus a[v]) \times (n - sz[v])\)
所以利用换根DP,可以 \(O(n)\) 处理该问题

posted on 2023-10-30 14:43  Qiansui  阅读(23)  评论(0编辑  收藏  举报