树上启发式合并
考虑一个问题:我们如何维护一颗子树里面有多少个不同的颜色呢?
直观想法,如果可以离线,那我们可以通过dfs序把树上问题转换成为序列问题,从而将问题变成如何维护一段区间内有多少种不同的颜色。
也就是说,这个题用树上莫队可写。
但是回忆树上莫队的复杂度带了log有可能伤不起。
所以,我们采用玄学数据结构:树上启发式合并 O(nlogn)
这种数据结构多用于:1,无修改操作;2,只查询子树的答案。
算法基本思想:
运用到了树链剖分的思想,我们考虑轻重儿子。
我们先遍历一遍x结点轻儿子,同时维护轻儿子的各个结点的值。
在遍历一遍x结点重儿子;
最后在遍历一遍x结点轻儿子,将此次轻儿子所产生的影响和遍历重儿子所产生的影响合并。得到x的值。
树链处理代码:
void dfs1(int u, int fa) {
size[u] = 1;
for (int i = head[u]; i; i = tree[i].next) {
int v = tree[i].v;
if (v != fa) {
dfs1(v, u);
size[u] += size[v];
if (size[v] > size[son[u]]) son[u] = v;
}
}
}
下为求答案代码:
int dfs2(int u, int fa, bool keep, bool isson) {
int tmp = 0;
for (int i = head[u]; i; i = tree[i].next) {
int v = tree[i].v;
if (v != fa && v != son[u]) {
dfs2(v, u, 0, 0);
}
}
if (son[u]) tmp += dfs2(son[u], u, 1, 1);
for (int i = head[u]; i; i = tree[i].next) {
int v = tree[i].v;
if (v != fa && v != son[u]) {
tmp += dfs2(v, u, 1, 0);
}
}
if (!check[color[u]]) {
tmp++;
check[color[u]] = 1;
}
if (!keep || isson) ans[u] = tmp;
if (!keep) memset(check, 0, sizeof(check)), tmp = 0;
return tmp;
}
例题:https://codeforces.com/problemset/problem/600/E
本题属于无修改,查询子树,所以我们考虑树上启发式合并。
先遍历轻儿子,维护上面各个结点的值。
在遍历重儿子。最后重新遍历轻儿子。
#include"stdio.h"
#include"string.h"
#include"math.h"
#include"algorithm"
using namespace std;
typedef long long ll;
const int N = 200010;
int head[N],ver[N * 4],Next[N * 4],tot;
int n,c[N];
int d[N],Size[N],son[N],far[N];
ll ans[N];int Son;
int cnt[N];
int max_sum;ll sum;
void add(int x,int y)
{
ver[++ tot] = y; Next[tot] = head[x];
head[x] = tot;
}
void dfs1(int x,int fa)
{
Size[x] = 1; far[x] = fa;
d[x] = d[fa] + 1;
for(int i = head[x]; i; i = Next[i]){
int y = ver[i];
if(y == fa) continue;
dfs1(y,x);
Size[x] += Size[y];
if(son[x] == 0 || Size[son[x]] < Size[y]){
son[x] = y;
}
}
}
void add(int x,int fa,int val){
cnt[c[x]] += val;
if(cnt[c[x]] > max_sum){
max_sum = cnt[c[x]];
sum = c[x];
} else if(max_sum == cnt[c[x]]){
sum += c[x];
}
for(int i = head[x]; i; i = Next[i]){
int y = ver[i];
if(y == fa || y == Son) continue;
add(y,x,val);
}
}
void dfs2(int x,int fa,int op)
{
for(int i = head[x]; i; i = Next[i]){
int y = ver[i];
if(y == fa || y == son[x]) continue;
dfs2(y,x,0);
}
if(son[x]){
dfs2(son[x],x,1);
Son = son[x];
}
add(x,fa,1);///将其子树除重儿子外的结点所产生的影响进行添加维护。重儿子的影响已经在dfs中保存了下来
ans[x] = sum;
Son = 0;
if(op == 0){
add(x,fa,-1);///如果op==0 证明是轻儿子遍历过来的,所以在得到了当前结点的值之后,要把这次的操作影响进行删除
sum = 0; max_sum = 0;
}
}
int main()
{
scanf("%d",&n);
for(int i = 1; i <= n; i ++)
scanf("%d",&c[i]);
for(int i = 1; i < n; i ++)
{
int x,y; scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
dfs1(1,1);
dfs2(1,1,0);
for(int i = 1; i <= n; i ++)
if(i == 1) printf("%lld",ans[i]);
else printf(" %lld",ans[i]);
printf("\n");
}
浙公网安备 33010602011771号