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;
}

原文地址:https://www.cnblogs.com/zhoushuyu/p/9069164.html

时间: 2024-08-28 22:04:43

dsu on tree总结的相关文章

Codeforces 600E. Lomsat gelral(Dsu on tree学习)

题目链接:http://codeforces.com/problemset/problem/600/E n个点的有根树,以1为根,每个点有一种颜色.我们称一种颜色占领了一个子树当且仅当没有其他颜色在这个子树中出现得比它多.求占领每个子树的所有颜色之和. 我们都知道可以$BST$启发式合并从而完美${O(nlogn^{2})}$,这太丑陋了. 那么$Dsu~~on~~tree$是在干啥呢? 找出树中每一个节点的重儿子,统计答案的时候优先进入每一个点的所有轻儿子,之后再进入重儿子,目的是保留重儿子所

dsu on tree

osu on tree? dsu on tree! 这种操作可以在O(nlogn)的时间内解决一些无修改子树询问问题. 咱知道把一棵树轻重链剖分后,树的轻链,重链都只有O(logn)个. 这个算法就是利用了这一点,递归处理时保留重儿子的信息,轻儿子的则重新计算. 乍一看感觉很暴力,但是实际上是O(nlogn)的. 来看几道题吧. CodeForces - 600E 1 #include<cstdio> 2 #include<vector> 3 #include<bitset&

图论-树上启发式合并(DSU On Tree)

Disjoint Set Union On Tree ,似乎是来自 Codeforces 的一种新操作,似乎被叫做"树上启发式合并". 在不带修改的有根树子树信息统计问题中,似乎树上莫队和这个 DSU On Tree 是两类常规操作. 先对树按轻重链剖分.对于每个节点,先计算轻儿子为根的子树信息,每次计算后消除影响,再去计算其他轻儿子.然后计算重儿子为根的子树信息,不消除影响,并把轻儿子们为根的子树信息加入,再合并这个节点本身的信息.由于一个大小 \(x\) 的子树被消除影响后,都把信

DSU on tree浅谈

DSU on tree 在之前的一次比赛中,学长向我们讲了了这样一个神奇的思想:DSU on tree(树上启发式合并),看上去就非常厉害--但实际上是非常暴力的一种做法;不过暴力只是看上去暴力,它在处理不带修改的子树统计问题时有着优秀的时间复杂度\(O(Nlog N)\),显然在处理这一类问题上,它是优于我们常用的\(dfs\)序后莫队,更关键是它十分好写. 算法实现: 首先对所有轻儿子的子树信息进行统计,然后暴力擦除所有轻儿子的影响.再统计重儿子为根的子树信息,并将轻儿子的信息合并起来,加上

树上统计treecnt(dsu on tree 并查集 正难则反)

题目链接 \(Description\) 给定一棵\(n(n\leq 10^5)\)个点的树. 定义\(Tree[L,R]\)表示为了使得\(L\sim R\)号点两两连通,最少需要选择的边的数量. 求\[\sum_{l=1}^n\sum_{r=l}^nTree[l,r]\] \(Solution\) 枚举每条边,计算它的贡献. 那么我们要判断有多少连续区间的点跨过这条边,并不好算,反过来去求在这条边的两侧分别有多少个连续区间. 那么显然有\(O(n^2)\)的做法,即对每条边DFS它的两侧,枚

Codeforces.600E.Lomsat gelral(dsu on tree)

题目链接 dsu on tree参见这. \(Description\) 给定一棵树.求以每个点为根的子树中,出现次数最多的颜色的和. \(Solution\) dsu on tree模板题. 用sum[i]表示出现次数为i的颜色的和,cnt[i]表示出现次数为i的颜色有多少个(其实有个Max表示当前最多的次数,和tm[i]就好了),然后就这样了.. 再写一遍dsu on tree大体过程:(设当前点为x) 计算轻儿子子树的答案,并删掉轻儿子的贡献(大多数时候): 计算重儿子子树的答案,保留重儿

Codeforces.741D.Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(dsu on tree 思路)

题目链接 \(Description\) 给定一棵树,每条边上有一个字符(a~v).对每个节点,求它的子树中一条最长的路径,满足 路径上所有边上的字符可以重新排列成一个回文串.输出其最长长度. \(n\leq 5\times10^5\). \(Solution\) 可以构成回文串,即要么所有字符都出现了偶数次,要么有一个出现了奇数次.其余都出现了偶数次. 转化为异或!把每个字符c(0~21)映射到1<<c上去. 令\(s[x]\)表示根节点到\(x\)路径上边权的异或和.那么路径\((u,v)

CF 600E. Lomsat gelral(dsu on tree)

解题思路 \(dsu\) \(on\) \(tree\)的模板题.暴力而优雅的算法,轻儿子的信息暴力清空,重儿子的信息保留,时间复杂度\(O(nlogn)\) 代码 #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<set> using namespace std; const int

Codeforces 600E - Lomsat gelral 「$Dsu \ on \ tree$模板」

With $Dsu \ on \ tree$ we can answer queries of this type: How many vertices in the subtree of vertex $v$ has some property in $O (n \log n)$ time (for all of the queries)? 这题写的是轻重儿子(重链剖分)版本的 $Dsu \ on \ tree$ 具体流程如下: 每次先递归计算轻儿子,再单独递归重儿子,计算完后轻儿子的一些信息