Dsu on tree 神奇的暴力

什么是dsu

这是一个很暴力很无脑的算法。
对于一棵树如果我们需要计算每个节点对应子树的信息。
由于每个父节点的信息来自每个子节点。我们来用以下的流程来合并信息。
Dsu on tree 神奇的暴力_第1张图片

为什么可以用dsu

显然对于一个节点它只会被合并( logn )次所以复杂度可以为 nlogn
例如我们要:统计子树内出现次数不少于k的元素个数。

void dfs_pre(int x,int f){
    sz[x]=1;
    L[x]=++num;
    dfsline[num]=col[x];
    for(int i=0;iint y=G[x][i];
        if(y==f)continue;
        dfs_pre(y,x);
        sz[x]+=sz[y];
    }R[x]=num;
}
void Del(int x){
    for(int i=Lt[x];i<=Rt[x];i++){
        int t=dfsLine[i];
        --Res[cnt[col[t]]--];
    }
}
void Add(int x){
    for(int i=Lt[x];i<=Rt[x];i++) {
        int t=dfsLine[i];
        ++Res[++cnt[col[t]]]; 
    }
}
void dsu(int x) {
    找到重儿子son[x]
    for(y is son of x){
        if(y!=son[x])dsu(y),Del(y);
        //解决了y的信息后从容器中删去y
    }
    dsu(son[x]);//不删除big对应的子树
    for(y is son of x){
        if(y!=son[x])Add(y)加入y这棵子树
    }
    加入x节点
    回答在x节点上的询问 
}

怎么用dsu

<1>我们发现我们一共只使用一个容器来维护size最大的元素的信息同样,我们可以使用其他数据结构,不一定是数组。
<2>如果题目的信息对其父节点的权值或性质没有要求我们就可以使用dsu搜集并计算。

举几个例子

<1>给定一棵树,每个节点有颜色 c[i] ,定义子树u中出现次数最多的颜色为u的主要颜色(可以有多个),求每个节点的主要颜色数值和。

我们维护一个最大值mx表次数最多的颜色的次数以及一个cnt[x]记录每个颜色的个数,为了O(1)的求出答案我们用res[x]记录出现次数为x的答案

void Add(int x){
    for(int i=L[x];i<=R[x];++i){
        res[++cnt[dfsline[i]]]+=dfsline[i];
        Max(mx,cnt[dfsline[i]]);
    }
}
void Del(int x){
    for(int i=L[x];i<=R[x];++i){
        if(cnt[dfsline[i]]==mx)--mx;
        res[cnt[dfsline[i]]--]-=dfsline[i];
    }
}
void dsu(int x,int f){
    mx=0;
    for(int i=0;iint y=G[x][i];
        if(y!=f&&y!=big){
            dsu(y,x);Del(y);
        }
    }if(son[x])dsu(big,x);
    for(int i=0;iint y=G[x][i];
        if(y!=f&&y!=son[x])Add(y);
    }
    res[++cnt[col[x]]]+=col[x];
    Max(mx,cnt[col[x]]);
    ans[x]=res[mx];
}
void Solve(){
    dfs_pre(1,0);
    dsu(1,0);
    for(int i=1;i<=n;i++)
        cout<" ";
}

<2>给定一棵树,已知每个节点的父亲节点和每个节点上的字符(给出一条字符串,全部为小写字母)。对于m个询问,判断能否排列属于子树x的,深度为d的节点上所有的字符,以组成一个回文串。

由于这次的询问是对一个子树一个深度上信息的询问所以我们可以直接用一个二进制数组 res[dep] 来储存信息。

bool check(int x){
    int res=0;
    while(x){
        if(x&1)res++;
        x>>=1;
    }return res<=1;
}   
void update(int x){
    for(int i=L[x];i<=R[x];i++){
        int t=dfsline[i];
        res[dep[t]]^=1<void dsu(int x,int f){
    for(int i=0;iint y=G[x][i];
        if(y!=f&&y!=big){
            solve(y,x);update(y);
        }
    }
    if(big)solve(big,x);
    for(int i=0;iint y=G[x][i];
        if(y!=f&&y!=big)update(y);
    }
    res[dep[x]]^=1<for(int i=0;ivoid solve(){
    init();
    dfs_pre();
    dsu(1,0);
    put_ans();
}

<3>给定一棵以1为根的带权树,求满足w[LCA(u,v)]=w[u]×w[v]w[LCA(u,v)]=w[u]×w[v]的{u,v}点对个数。({u,v}={v,u},u≠≠v)

很容易发现只要我们把子树依次放入容器后,再把每个个子树上的信息和其他子树上的信息合并。
即用cnt[x]记录x出现的次数,对于一个需要加入的子树我们把(val[fa]=x*y)cnt[x]*son_tree_imformation[y]更新答案。


inline void Del(int x){
    for(int i=L[x];i<=R[x];i++)
        --cnt[col[dfsline[i]]];
}
inline void Add(int x,int nw){
    for(int i=L[x];i<=R[x];i++){
        int t=col[dfsline[i]];
        if(nw%T[t]==0){
            int rank=lower_bound(T+1,T+1+m,nw/T[t])-T;
            if(rank<=m&&T[rank]==nw/T[t])ans+=cnt[rank];
        }
    }
    for(int i=L[x];i<=R[x];i++)
        ++cnt[col[dfsline[i]]];
}
void solve(int x,int f){
    int big=0;
    for(int i=0;iint y=G[x][i];
        if(f==y)continue;
        if(sz[big]for(int i=0;iint y=G[x][i];
        if(y!=f&&y!=big){
            solve(y,x);Del(y);
        }
    }
    if(big)solve(big,x);

    for(int i=0;iint y=G[x][i];
        if(y!=f&&y!=big)Add(y,T[col[x]]);
    }
    if(T[1]==1)ans+=cnt[1];
    cnt[col[x]]++;
}
int main(){
    int a,b;Rd(n);
    for(int i=1;ifor(int i=1;i<=n;i++){
        Rd(col[i]);
        T[i]=col[i];
    }
    sort(T+1,T+n+1);
    m=unique(T+1,T+n+1)-T-1;
    for(int i=1;i<=n;i++)
        col[i]=lower_bound(T+1,T+1+m,col[i])-T;
    dfs_pre(1,0);
    solve(1,0);
    cout<#define f first
#define s second
#define pb push_back
typedef long long ll;
using namespace std;
ll A[100011];
vector<int>G[100011];
mapres[100011];
ll ans=0;
int cnt=0;
int dfs(int v,int p){//返回v所存储的下表 
    cnt++;
    int big = v;
    res[v][A[v]]++;
    ll cur=0;
    for(int i=0;iif(G[v][i]!=p){
            int x = dfs(G[v][i],v);

if(res[big].size()for(map::iterator it=res[x].begin();it!=res[x].end();it++)
                if(A[v]%it->f==0)//找原子树中没有的元素 
                    if(res[big].find(A[v]/it->f)!=res[big].end())
                        ans+=it->s *res[big][A[v]/it->f];


            for(map::iterator it=res[x].begin();it!=res[x].end();it++)
                res[big][it->f]+=it->s;
        }   
    }
    return big;
}
int main(){
    int n,u,v;
    cin >> n;
    for(int i=1;icin>>u>>v;
        G[u].pb(v);
        G[v].pb(u);
    }
    for(int i=1;i<=n;i++)cin>>A[i];
    dfs(1,0);
    cout<

问一个树上在它深度d以下节点的个数。
用Bit来维护即可

inline void add(int i,int v){
    while(i<=n)Bit[i]+=v,i+=i&-i;
}
inline int sum(int i){
    int res=0;
    while(i)res+=Bit[i],i-=i&-i;
    return res;
}
void dfs(int x,int f){
    dep[x]=dep[f]+1;
    for(register int i=H[x];i;i=T[i])
        ans[id[i]]-=sum(d[i]+dep[x]);
    add(dep[x],val[x]);
    for(register int i=h[x];i;i=nx[i])dfs(G[i],x);
    for(register int i=H[x];i;i=T[i])
        ans[id[i]]+=sum(d[i]+dep[x]);
}
int main(){
    Rd(n);Rd(m);
    for(int i=1;i<=n;i++)
        Rd(val[i]);
    for(int i=2;i<=n;i++){
        Rd(a);
        p_G(a,i);
    }
    for(int i=1;i<=m;i++){
        Rd(a);Rd(b);
        p_Q(a,i,b);
    }
    dfs(1,0);
    for(int i=1;i<=m;i++)
        printf("%d\n",ans[i]);
}

你可能感兴趣的:(算法模板,dsu,树)