Dsu on tree学习笔记

前置知识:重链剖分

Dsu on tree

算法简介

考虑这样的一类树上统计问题,无修改且允许离线,统计子树信息。

这就是dsu on tree 处理的问题。

算法流程

考虑暴力怎么做,遍历每个子节点的贡献->处理答案->删除子节点贡献。

dsu on tree的实现 :

  1. 遍历所有轻儿子,递归时删除贡献。
  2. 遍历重儿子,保留贡献。
  3. 处理轻儿子的贡献。
  4. 处理当前节点的答案。
  5. 如果该点为轻儿子,删除该点贡献。

为什么不保留多个节点的贡献?

因为会混淆子树之间的信息,由于我们最后处理重儿子的答案,所以递归之后当前子树需要用到他的重儿子贡献,如果都保存的话,遍历下一个子树时会将当前子树的贡献也算进去,所以只能保留一个子树的答案,因为重儿子的子树大小最大,所以保存下之后递归回去需要处理的子节点数更少,从而达到降低复杂度的目的。

主体框架

void dfs1(int u) { // 重剖
	siz[u] = 1;
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].v; 
		dfs1(v); siz[u] += siz[v]; if (siz[son[u]] < siz[v]) son[u] = v;
	}
}
void dfs2(int u, int fa, int opt) { // opt=1表示保留贡献,否则表示删除
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].v; if (v == son[u] || v == fa) continue ;
		dfs2(v, u, 0);//处理轻儿子答案
	}
	if (son[u]) dfs2(son[u], u, 1), Son = son[u];//处理重儿子答案并保存
	calc(u);//统计轻儿子贡献
  /*
处理当前节点答案
  */ 
	Son = 0;
	if (!opt) del(u); // 如果该点轻儿子,删除贡献
//注意:轻儿子的重子节点也要删
}

这里不懂没关系,结合例题再去理解。

例题

CF600E Lomsat gelral

题目描述

给你一棵以结点 \(1\) 为根的有根树,每个节点最开始都被涂上了颜色。

如果颜色 \(c\) 在以结点 \(v\) 为根的子树中出现次数最多,则称其在以结点 \(v\) 为根的子树中占重要地位。一棵树中可以有很多颜色同时占重要地位

\(v\) 为根的子树指结点 \(v\) 及其他到根结点的路径包含 \(v\) 的结点。

请输出对于每一个结点 \(v\),在其子树中占重要地位的颜色编号之和。

Solution

板子,没啥好说的。

细节见代码 code

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n, c[N], siz[N], son[N], mx, sum, cnt[N], Son, ans[N];
vector<int> g[N];
void dfs1(int u, int fa) {
	siz[u] = 1;
	for (auto v : g[u]) {
		if (v == fa) continue ;
		dfs1(v, u);
		siz[u] += siz[v];
		if (siz[son[u]] < siz[v]) son[u] = v;
	}
}
inline void add(int u, int fa, int val) {
	cnt[c[u]] += val;
	if (cnt[c[u]] > mx) mx = cnt[c[u]], sum = c[u];
	else if (cnt[c[u]] == mx) sum += c[u];
	for (auto v : g[u]) {
		if (v == fa || v == Son) continue ;
		add(v, u, val);
	}
}
void dfs2(int u, int fa, int opt) {
	for (auto v : g[u]) {
		if (v == fa || v == son[u]) continue ;
		dfs2(v, u, 0);
	}
	if (son[u]) dfs2(son[u], u, 1), Son = son[u];
	add(u, fa, 1), Son = 0;
	ans[u] = sum;
	if (!opt) add(u, fa, -1), sum = 0, mx = 0;
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> c[i];
	for (int i = 1, u, v; i < n; i++) {
		cin >> u >> v;
		g[u].push_back(v), g[v].push_back(u);
	}
	dfs1(1, 0), dfs2(1, 0, 0);
	for (int i = 1; i <= n; i++) cout << ans[i] << " ";
	return 0;
}

P9233 [蓝桥杯 2023 省 A] 颜色平衡树

题目描述

给定一棵树,结点由 \(1\)\(n\) 编号,其中结点 \(1\) 是树根。树的每个点有一个颜色 \(C_i\)

如果一棵树中存在的每种颜色的结点个数都相同,则我们称它是一棵颜色平衡树。

求出这棵树中有多少个子树是颜色平衡树。

Solution

也是模板,用一个桶维护每种颜色出现了多少次,再维护一下出现的颜色数量,如果 \(cnt_c \times tot = siz_u\),答案加 \(1\)

其中 \(cnt_c\) 为颜色 \(c\) 的出现次数,\(tot\) 为颜色数,\(siz_u\) 为当前节点 \(u\) 的子树大小。

code

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define rd read()
using namespace std;
const int N = 2e5 + 5;
struct Edge {
	int v, next;
} e[N];
inline int read() {
	int x = 0; char ch = getchar();
	while (ch < '0' || ch > '9') ch = getchar();
	while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x;
}
int ans, c[N], n, head[N], cnt, siz[N], son[N], Son, col[N], tot;
inline void add(int u, int v) {e[++cnt] = {v, head[u]}; head[u] = cnt;}
void dfs1(int u) {
	siz[u] = 1;
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].v; 
		dfs1(v); siz[u] += siz[v]; if (siz[son[u]] < siz[v]) son[u] = v;
	}
}
inline void calc(int u, int val) {
	col[c[u]] += val;
	if (col[c[u]] == 1 && val == 1) tot++; 
	if (col[c[u]] == 0 && val == -1) tot--;
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].v; if (v == Son) continue ;
		calc(v, val);
	}
}
void dfs2(int u, int opt) {
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].v; if (v == son[u]) continue ;
		dfs2(v, 0);
	}
	if (son[u]) dfs2(son[u], 1), Son = son[u];
	calc(u, 1);
	if (tot * col[c[u]] == siz[u]) ans++;
	Son = 0;
	if (!opt) calc(u, -1), tot = 0;
}
signed main() {
	n = rd; for (int i = 1; i <= n; i++) c[i] = rd, add(rd, i);
	dfs1(1); dfs2(1, 0);
	cout << ans;
	return 0;
}
posted @ 2025-09-27 11:05  Xycxxx  阅读(45)  评论(0)    收藏  举报