树上启发式合并与dsu on tree

题目链接:https://codeforc.es/contest/600/problem/E

题目大意:给一颗以1为根的树,共n个点。点有颜色,颜色从1-n编号。问每颗子树中出现次数最多的颜色 的编号之和。

启发式合并做法:

    #include<bits/stdc++.h>
    #include<ext/pb_ds/assoc_container.hpp>
    #include<ext/pb_ds/hash_policy.hpp>
    #define ll long long
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define pb push_back
    using namespace std;
    using namespace __gnu_pbds;
    const int N = 1e5+1000;
    vector<int>nxt[N];
    int siz[N],son[N];
    void split(int u,int f) {
        son[u] = 0;
        siz[u] = 1;
        for(auto v:nxt[u]) {
            if(v==f)continue;
            split(v,u);
            siz[u] += siz[v];
            if(siz[son[u]]<siz[v]) son[u] = v;
        }
    }
    //----------
    ll val[N],ans[N],p[N];
    unordered_map<ll,ll>num[N],cnt[N]; 
    void add(int u,int f,int t) {
        int col = val[u];
        cnt[t][num[t][col]] -= col;
        num[t][col]++;
        cnt[t][num[t][col]] += col;
        if(num[t][col]>p[t]) p[t]++;
        for(auto v:nxt[u]) {
            if(v==f) continue;
            add(v,u,t);
        }
    }
    void dfs(int u,int f) {
        for(auto v:nxt[u]) {
            if(v==f) continue;
            dfs(v,u);
        }
        if(son[u]) {                    //继承重儿子的信息
            swap(num[u],num[son[u]]);
            swap(cnt[u],cnt[son[u]]);
            swap(p[u],p[son[u]]);
        }
        for(auto v:nxt[u]) {            
            if(v==f||v==son[u]) continue;
            add(v,u,u);                //暴力合并轻儿子
        }    
        int col = val[u];                //和并这个点本身
        cnt[u][num[u][col]] -= col;
        num[u][col]++;
        cnt[u][num[u][col]] += col;
        if(num[u][col]>p[u]) p[u]++;
        ans[u] = cnt[u][p[u]];
    }
    int main() {
        //ios::sync_with_stdio(0);
        int n;
        cin>>n;
        rep(i, 1, n) cin>>val[i];
        rep(i, 1, n-1) {
            int u,v;
            cin>>u>>v;
            nxt[u].pb(v);
            nxt[v].pb(u);
        }
        split(1,0);
        dfs(1,0);
        rep(i, 1, n) cout<<ans[i]<<' ';
        return 0;
    }

dsu on tree 做法:

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define mp make_pair
#define pb push_back
#define ll long long
using namespace std;
const int N = 1e5+1000;
vector<int>nxt[N];
int siz[N],son[N];
void split(int u,int f) {
    son[u] = 0;
    siz[u] = 1;
    for(auto v:nxt[u]) {
        if(v==f)continue;
        split(v,u);
        siz[u] += siz[v];
        if(siz[son[u]]<siz[v]) son[u] = v;
    }
}
//---------------------
ll p,num[N],ans[N],Son,cnt[N],val[N];
void add(int u,int f,int data) {
    cnt[num[val[u]]] -= val[u];
    num[val[u]] += data;
    cnt[num[val[u]]] += val[u];
    if(num[val[u]]>p) p++;
    for(auto v:nxt[u]) {
        if(v==f||v==Son) continue;
        add(v,u,data);
    }
}
void dfs(int u,int f,int opt) {
    for(auto v:nxt[u]) {
        if(v==f||v==son[u]) continue;
        dfs(v,u,0);              //计算轻儿子的答案,不保留贡献
    }
    if(son[u]) dfs(son[u],u,1),Son = son[u];  //计算重儿子的答案,保留贡献
    add(u,f,1),Son = 0;          //遍历所有轻儿子,将贡献合并
    ans[u] = cnt[p];             //记录答案
    if(!opt) add(u,f,-1),p = 0;  //如果u点是轻儿子,消除全部影响,清空所有信息
}
int main() {
    //freopen("a.txt","r",stdin);
    ios::sync_with_stdio(0);
    int n;
    cin>>n;
    rep(i, 1, n) cin>>val[i];
    rep(i, 1, n-1) {
        int u,v;
        cin>>u>>v;
        nxt[u].pb(v);
        nxt[v].pb(u);
    }
    split(1,0);
    dfs(1,0,0);
    rep(i, 1, n) cout<<ans[i]<<' ';
    return 0;
}

 

上一篇:安全路径——最短路径树+dsu缩边


下一篇:题解 | Checkers-2019牛客暑期多校训练营第九场G题