1. 程式人生 > 其它 >【模板】dsu on tree CF600E 樹上啟發式合併

【模板】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;
}