dsu on tree总结

Posted zhoushuyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dsu on tree总结相关的知识,希望对你有一定的参考价值。

dsu on tree

树上启发式合并。我并不知道为什么要叫做这个名字。。。

干什么的

可以在\(O(n\log n)\)的时间内完成对子树信息的询问,可横向对比把树按\(dfs\)序转成序列问题的\(O(n\sqrt n)\)莫队算法。

怎么实现

\(dfs\)到一个点\(u\),执行以下操作:

1、递归处理所有轻儿子;
2、递归处理重儿子;
3、计算整棵子树的贡献(在第2步中重儿子的贡献得以保留,所以不需要重复计算);
4、若点\(u\)不是其父亲的重儿子,删除整棵子树的贡献。

看上去像是暴力?

复杂度分析

只有当存在一条轻边时,这条轻边所连接的子树才需要被重新计算一次贡献。那么一个点被重复计算的次数就等于这个点到根路径上经过的轻边条数。而根据轻重链剖分那套理论,一个点到根路径上的轻边不会超过\(\log n\)条,所以这个算法的复杂度为\(O(n\log n*t)\),其中\(t\)为一次计算贡献的复杂度。

题目

懒得分开写博客了qaq

CF600E Lomsat gelral

luogu
一棵树有\(n\)个结点,每个结点都有一种颜色,求每个子树中出现次数最多的颜色(们)的编号之和。

sol:维护\(num_i\)表示有多少种颜色出现了\(i\)次,\(sum_i\)表示这些颜色的编号和。显然计算贡献是\(O(1)\)的。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define ll long long
const int N = 1e5+5;
int n,a[N],to[N<<1],nxt[N<<1],head[N],cnt;
int sz[N],son[N],vis[N],tot[N],num[N],Max;ll sum[N],ans[N];
void link(int u,int v){
    to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}
void dfs1(int u,int f){
    sz[u]=1;
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=f){
            dfs1(to[e],u);sz[u]+=sz[to[e]];
            if (sz[to[e]]>sz[son[u]]) son[u]=to[e];
        }
}
void update(int u,int f,int val){
    int &t=tot[a[u]];
    --num[t];sum[t]-=a[u];
    t+=val;
    ++num[t];sum[t]+=a[u];
    if (val>0) Max=max(Max,t);
    else if (!num[Max]) --Max;
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=f&&!vis[to[e]])
            update(to[e],u,val);
}
void dfs(int u,int f,int keep){
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=f&&to[e]!=son[u])
            dfs(to[e],u,0);
    if (son[u]) dfs(son[u],u,1),vis[son[u]]=1;
    update(u,f,1);
    ans[u]=sum[Max];
    vis[son[u]]=0;
    if (!keep) update(u,f,-1);
}
int main(){
    n=gi();
    for (int i=1;i<=n;++i) a[i]=gi();
    for (int i=1;i<n;++i){
        int u=gi(),v=gi();
        link(u,v);link(v,u);
    }
    dfs1(1,0);dfs(1,0,1);
    for (int i=1;i<=n;++i) printf("%I64d ",ans[i]);
    puts("");return 0;
}

CF570D Tree Requests

luogu
给你一棵\(n\)个节点的树,每个节点上有一个小写英文字母。每次询问\(v_i,h_i\),表示在\(v_i\)子树中所有深度为\(h_i\)的点上的字母拿出来重新组合能否形成一个回文串。

sol:一些字母重新组合后能够形成回文串当且仅当存在至多一个字母的出现次数为奇数。所以可以把每个小写英文字母当作是一个二进制位然后维护子树中每个深度的异或和即可。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 5e5+5;
struct node{int h,nxt;}a[N];
int n,m,nxt[N],head[N],val[N],ft[N];char s[N];
int dep[N],sz[N],son[N],vis[N],sum[N],ans[N];
void add(int v,int h,int id){
    a[id]=(node){h,ft[v]};ft[v]=id;
}
void dfs1(int u,int f){
    dep[u]=dep[f]+1;sz[u]=1;
    for (int v=head[u];v;v=nxt[v]){
        dfs1(v,u),sz[u]+=sz[v];
        if (sz[v]>sz[son[u]]) son[u]=v;
    }
}
void update(int u){
    sum[dep[u]]^=val[u];
    for (int v=head[u];v;v=nxt[v])
        if (!vis[v]) update(v);
}
bool cal(int x){
    int res=0;
    while (x) ++res,x^=x&-x;
    return res<=1;
}
void dfs(int u,int f,int keep){
    for (int v=head[u];v;v=nxt[v])
        if (v!=son[u]) dfs(v,u,0);
    if (son[u]) dfs(son[u],u,1),vis[son[u]]=1;
    update(u);
    for (int i=ft[u];i;i=a[i].nxt) ans[i]=cal(sum[a[i].h]);
    vis[son[u]]=0;
    if (!keep) update(u);
}
int main(){
    n=gi();m=gi();
    for (int i=2,f;i<=n;++i)
        f=gi(),nxt[i]=head[f],head[f]=i;
    scanf("%s",s+1);
    for (int i=1;i<=n;++i) val[i]=1<<s[i]-'a';
    for (int i=1,v,h;i<=m;++i) v=gi(),h=gi(),add(v,h,i);
    dfs1(1,0);dfs(1,0,1);
    for (int i=1;i<=m;++i) puts(ans[i]?"Yes":"No");
    return 0;
}

CF208E Blood Cousins

luogu
给你一片森林,每次询问和一个点有相同的\(k\)次祖先的点有多少个。

sol:倍增跳到那个祖先上然后就是只需要知道该深度有多少个点就行了。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 1e5+5;
int n,m,nxt[N],head[N],root[N],fa[18][N],ft[N];
int dep[N],sz[N],son[N],vis[N],tot[N],ans[N];
struct node{int d,nxt;}a[N];
void add(int x,int d,int id){
    a[id]=(node){d,ft[x]};ft[x]=id;
}
void dfs1(int u,int f){
    fa[0][u]=f;dep[u]=dep[f]+1;sz[u]=1;
    for (int i=1;i<=17;++i) fa[i][u]=fa[i-1][fa[i-1][u]];
    for (int v=head[u];v;v=nxt[v]){
        dfs1(v,u);sz[u]+=sz[v];
        if (sz[v]>sz[son[u]]) son[u]=v;
    }
}
void update(int u,int val){
    tot[dep[u]]+=val;
    for (int v=head[u];v;v=nxt[v])
        if (!vis[v]) update(v,val);
}
void dfs(int u,int keep){
    for (int v=head[u];v;v=nxt[v])
        if (v!=son[u]) dfs(v,0);
    if (son[u]) dfs(son[u],1),vis[son[u]]=1;
    update(u,1);
    for (int i=ft[u];i;i=a[i].nxt) ans[i]=tot[a[i].d]-1;
    vis[son[u]]=0;
    if (!keep) update(u,-1);
}
int main(){
    n=gi();
    for (int i=1;i<=n;++i){
        int f=gi();
        if (!f) root[++root[0]]=i;
        else nxt[i]=head[f],head[f]=i;
    }
    for (int i=1;i<=root[0];++i) dfs1(root[i],0);
    m=gi();
    for (int i=1;i<=m;++i){
        int x=gi(),k=gi(),d=dep[x];
        for (int j=0;j<=17;++j) if (k&(1<<j)) x=fa[j][x];
        add(x,d,i);
    }
    for (int i=1;i<=root[0];++i) dfs(root[i],0);
    for (int i=1;i<=m;++i) printf("%d ",ans[i]);
    puts("");return 0;
}

以上是关于dsu on tree总结的主要内容,如果未能解决你的问题,请参考以下文章

dsu on tree(无讲解)

CF 600E. Lomsat gelral(dsu on tree)

dsu on tree

[探究] dsu on tree,一类树上离线问题的做法

dsu on tree

初涉DSU on tree