【模板】dsu on tree CF600E 树上启发式合并

依稀记得这东西在NOIP前某范姓大佬用过。。然而居然之后再无出现过。。。还是写一下吧,,还是挺有趣的。


题目连接

参考博客1

参考博客2

这用于解决一类查询子树无修问题,时间复杂度O(nlogn),本质上与其他的启发式合并是类似的,用小的块合并大的块上,这样由于每次翻倍大小保证每个点只会被用到logn次。更具体的,每个点被统计只会在经过轻边暴力统计(经过轻边顶多logn次到root)或者重儿子统计1次,那么一个点最多统计logn次。

在树上可以想到一个显然的想法是将轻儿子合并到重儿子身上。我们只维护一个数组,保证处理到某个点上的时候我们都已经拿到了重儿子的信息之后再用轻儿子去合并。

而实现上我们先处理完轻儿子然后消除信息清空数组再遍历重儿子就可以实现拿到的是重儿子的信息。

具体上dfs序和直接树遍历都是ok的。

点击查看代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
#include<bitset>
#include<queue>
#include<vector>
#include<cstdio>
#include<set>
#include<map>
#include<cmath>
#include<random>
#include<ctime>
#include<complex>
#include<iomanip>

using namespace std;
const int maxn = 2e5+5;
typedef long long ll;
vector<int>ve[maxn];
int n,col[maxn];
int zerz[maxn],siz[maxn];
void dfs(int x,int par) {
    siz[x] = 1;
    for(int y:ve[x]) {

        if(y==par) continue;
        dfs(y,x);
        siz[x] += siz[y];
        if(siz[y]>siz[zerz[x]]) zerz[x] = y;
    }
}
int MX,SON; ll SM,ANS[maxn];
int cnt[maxn];
void advl(int x,int par,int vl) {
    cnt[col[x]] += vl;
    if(cnt[col[x]]>MX) MX=cnt[col[x]],SM = col[x];
    else if(cnt[col[x]]==MX) SM += col[x];
    for(int y:ve[x]) {
        if(y==par||y==SON) continue;
        advl(y,x,vl);
    }
}
void dfs2(int x,int par,int opt) {
    for(int y:ve[x]) {
        if(y==par||y==zerz[x]) continue;
        dfs2(y,x,0);
    }
    if(zerz[x]) dfs2(zerz[x],x,1),SON=zerz[x];
    advl(x,par,1);
    SON = 0;
    ANS[x] = SM;
    if(opt==0) advl(x,par,-1),SM=0,MX=0;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        scanf("%d",&col[i]);
    }
    for(int i=1;i<n;i++) {
        int x,y; scanf("%d%d",&x,&y);
        ve[x].push_back(y);
        ve[y].push_back(x);
    }
    dfs(1,0); dfs2(1,0,1);
    for(int i=1;i<=n;i++) {
        printf("%lld ",ANS[i]);
    }
    return 0;
}
posted @ 2021-10-29 17:03  Newuser233  阅读(37)  评论(0编辑  收藏  举报