Dsu on tree

前言

今晚抽了点时间学了一下dsu on tree
大概可以用在解决子树信息的问题,并且不可以带修改

例题

CF的题
题目大意
n个点的有根树,以1为根,每个点有一种颜色。我们称一种颜色占领了一个子树当且仅当没有其他颜色在这个子树中出现得比它多。求占领每个子树的所有颜色之和。

过程

我们可以先考虑暴力怎么做,你肯定是对于一个点,暴力访问他子树里面所有的点,然后算一下代价对吧,然后再把子树里面的信息全部删除
这样的话是 n2
然后我们发现,其实有很多时候,一些删除是浪费的,因为很快就会在他父亲那里又加回来
于是我们考虑树链剖分
然后对于一个点的重孩子,就保留它子树里面全部的信息
也就是说,我们先遍历所有轻儿子,然后,遍历完以后就删除它的影响
然后最后遍历它的重儿子,然后他的重儿子就不还原了,直接拿去父亲那里算代价
然后对于一个点,它所有重孩子已经弄完了,只需要暴力跑一下它轻儿子的所有子树就好了

时间复杂度

从上面的过程,这个算法看起来是十分不优秀的,因为你会觉得,一个点都要暴力算轻儿子的子树的所有代价,好像很慢
那我们可以换一个想法,考虑每一个节点被算了多少次
我们知道,当这个点的祖先,有一个不是他父亲的轻儿子,那么它就要被重新算一次
又因为一个点到跟的路径不会超过log条轻链
这个证明也很简单
因为一个轻儿子的子树大小,肯定不会超过一个子树的一般
所以一个轻儿子的子树与它父亲子树的大小,至少是两倍的关系
所以你最多跳log个轻链就没了
于是一个点最多会被暴力算log次

上面那道题的代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const LL N=100005;
LL n;
LL c[N];
struct qq
{
    LL x,y,last;
}e[N<<1];LL num,last[N];
void init (LL x,LL y)
{
    num++;
    e[num].x=x;e[num].y=y;
    e[num].last=last[x];
    last[x]=num;
}
LL son[N],tot[N];
void dfs1 (LL x,LL ff)
{
    tot[x]=1;
    for (LL u=last[x];u!=-1;u=e[u].last)
    {
        LL y=e[u].y;
        if (y==ff) continue;
        dfs1(y,x);
        tot[x]+=tot[y];
        if (tot[son[x]]<tot[y]) son[x]=y;
    }
}
bool vis[N];//这棵子树是否还存在 
LL shen[N],sum,maxx;
void change (LL x,LL ff,LL k)
{
    shen[c[x]]+=k;
    if (k>0&&shen[c[x]]>=maxx)
    {
        if (shen[c[x]]>maxx) sum=0,maxx=shen[c[x]];
        sum=sum+c[x];
    }
    for (LL u=last[x];u!=-1;u=e[u].last)
    {
        LL y=e[u].y;
        if (y==ff||vis[y]) continue;
        change(y,x,k);
    }
}
LL ans[N];
void dfs2 (LL x,LL ff,bool lalal)//那个点   父亲是谁   是不是重儿子 
{
    for (LL u=last[x];u!=-1;u=e[u].last)
    {
        LL y=e[u].y;
        if (y==ff||y==son[x]) continue;
        dfs2(y,x,false);
    }
    if (son[x]!=0)  dfs2(son[x],x,true),vis[son[x]]=true;
    change(x,ff,1);ans[x]=sum;
    if (son[x]!=0) vis[son[x]]=false;
    if (lalal==false)//如果他是一个轻儿子,消除影响
        change(x,ff,-1),maxx=sum=0;
}
int main()
{
    num=0;memset(last,-1,sizeof(last));
    scanf("%lld",&n);
    for (LL u=1;u<=n;u++) scanf("%lld",&c[u]);
    for (LL u=1;u<n;u++)
    {
        LL x,y;
        scanf("%lld%lld",&x,&y);
        init(x,y);init(y,x);
    }
    dfs1(1,0);
    dfs2(1,0,false);
    for (LL u=1;u<=n;u++)
        printf("%lld ",ans[u]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值