dsu on tree 和 莫队算法很像,哪里像?出于暴力而胜于暴力。。。
dsu on tree 也叫树上启发式合并,借用了并查集(dsu)合并的思想,因此叫dsu on tree
思路也很简洁,第一步也是树链剖分的第一步,求重儿子。
然后就是解决问题。大致思路是,优先处理轻儿子,将轻儿子处理出来以后,再处理重儿子。然而因为同一个点的轻重儿子是平行的,往往信息不相关,因此为了避免不同儿子之间信息共享后导致无法得到后续儿子节点的真实信息,因此每次处理完轻儿子后需要再减去它的贡献(或者开一个二维数组都保存下来,但是往往会MLE),处理完重儿子后,信息保留,这样我们就保留了当前子树的最大的儿子的信息,然后暴力统计所有轻儿子的贡献,即为当前节点的贡献。(就是暴力QAQ)
看题吧QAQ、、
CF600E 题目翻译
一棵树有n个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和。
说明一下,dsu on tree适用于离线、对子树信息的询问。
树链剖分dfs
int son[N], siz[N];
void dfs(int x, int fa){
siz[x] = 1;
int m = -1;
for (int i = head[x]; i; i = nex[i]){
int y =to[i];
if (y == fa)continue;
dfs(y, x);
siz[x] += siz[y];
if (m == -1 || m < siz[y]){
m = siz[y];
son[x] = y;
}
}
}
dsu on tree 主体部分
void dfs(int x, int fa, bool f){
for (int i = head[x]; i; i = nex[i]){
int y = to[i];
if (y == fa || y == son[x])continue;
// 跳过重儿子,即优先处理轻儿子
dfs(y, x, false);
}
if (son[x]){
// 处理重儿子
dfs(son[x], x, true);
Son = son[x];
// 这里设这个标记是为了在cal函数中用
// 下面会写为什么要这么写
}
cal(x, fa, 1);// 计算x的贡献
Son = 0;
ans[x] = sum;
if (!f){
cal(x, fa, -1);// 如果是轻儿子删除贡献
sum = 0;
mx = 0;
}
}
在上图中,对于根节点1而言,3号节点是重儿子,而优先处理轻儿子,因此要求解2为根的子树,2为根的子树中的节点都需要求解,也就是说只需要避开1号节点的重儿子即可,但是我们是递归操作的,因此需要一个变量来记录它的重儿子是谁,如果直接用
t
o
≠
s
o
n
[
x
]
to \neq son[x]
to=son[x],因为每次递归x都在变化,所以不能这样操作。
下面的Son = 0,它是在删除贡献的时候起作用的,例如,我们要删除x为根的子树的贡献(即x是它父节点的轻儿子),需要删除干净,包括x的重儿子的贡献在内,而求解之前,刚刚处理了重儿子,此时Son就是x节点的重儿子,如果不讲Son置为0,可以发现,在cal函数中也跳过了Son这个点(因为重儿子贡献被保留了,不需要重复计算),若不改,则删不干净。
int num[N], color[N];
void cal(int x, int fa, int f){
num[color[x]] += f;
if (mx < num[color[x]]){
mx = num[color[x]];
sum = color[x];
}
else if (mx == num[color[x]]){
sum += color[x];
}
for (int i = head[x]; i; i = nex[i]){
int y = to[i];
if (y == fa || y == Son)continue;
cal(y, x, f);
}
}
递归的计算下去就行了
完整代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mem(a, b) memset(a, b, sizeof a)
const int N = 1e5 + 10;
int head[N], nex[N * 2], to[N * 2], cnt;
void add(int a, int b){
to[++cnt] = b;
nex[cnt] = head[a];
head[a] = cnt;
}
int son[N], siz[N];
void dfs(int x, int fa){
siz[x] = 1;
int m = -1;
for (int i = head[x]; i; i = nex[i]){
int y =to[i];
if (y == fa)continue;
dfs(y, x);
siz[x] += siz[y];
if (m == -1 || m < siz[y]){
m = siz[y];
son[x] = y;
}
}
}
ll ans[N], sum, mx, Son;
int num[N], color[N];
void cal(int x, int fa, int f){
num[color[x]] += f;
if (mx < num[color[x]]){
mx = num[color[x]];
sum = color[x];
}
else if (mx == num[color[x]]){
sum += color[x];
}
for (int i = head[x]; i; i = nex[i]){
int y = to[i];
if (y == fa || y == Son)continue;
cal(y, x, f);
}
}
void dfs(int x, int fa, bool f){
for (int i = head[x]; i; i = nex[i]){
int y = to[i];
if (y == fa || y == son[x])continue;
dfs(y, x, false);
}
if (son[x]){
dfs(son[x], x, true);
Son = son[x];
}
cal(x, fa, 1);
Son = 0;
ans[x] = sum;
if (!f){
cal(x, fa, -1);
sum = 0;
mx = 0;
}
}
int n;
int main()
{
// freopen("in.in", "r", stdin);
// freopen("out.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++)scanf("%d", color + i);
for (int i = 1; i < n; i++){
int a, b;
scanf("%d %d", &a, &b);
add(a, b);
add(b, a);//
}
dfs(1, -1);
dfs(1, -1, 1);
for (int i = 1; i <= n; i++){
printf("%I64d ", ans[i]);
}
return 0;
}
题目中没说,输入的节点之间有父子关系,只能知道连边了,因此需要无向图,有向图GG。。。