[codeforces600E]Lomsat gelral(dsu on the tree+讲解)

题目:

我是超链接

题意:

一棵树,每一个点有一个颜色,统计以每一个节点为根的子树中出现次数最多的颜色的编号和。

题解:

基本就是dsu的裸题啦,下面讲解见咯

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#define LL long long
#define N 100005
using namespace std;
int tot,nxt[N*2],point[N],v[N*2],size[N],son[N],cnt[N],a[N],maxx,Son;
LL sum,ans[N];
void addline(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void getson(int x,int fa)
{
    size[x]=1;
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa)
      {
        getson(v[i],x);
        if (size[v[i]]>size[son[x]]) son[x]=v[i];
        size[x]+=size[v[i]];
      }
}
void add(int x,int fa,int vv)
{
    cnt[a[x]]+=vv;
    if (cnt[a[x]]>maxx) sum=(LL)a[x],maxx=cnt[a[x]];
    else if (cnt[a[x]]==maxx) sum+=(LL)a[x];
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa && v[i]!=Son) add(v[i],x,vv);
}
void dfs(int x,int fa,int k)
{
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa && v[i]!=son[x]) dfs(v[i],x,0); 
    if (son[x])//不是叶子,有重儿子 
      dfs(son[x],x,1),Son=son[x];
    add(x,fa,1);//是叶子||轻儿子就加入贡献,并不会删除贡献 
    Son=0;
    ans[x]=sum;
    if (!k) add(x,fa,-1),maxx=sum=0;//轻儿子 
}
int main()
{
    int n,i;
    scanf("%d",&n);
    for (i=1;i<=n;i++) scanf("%d",&a[i]);
    for (i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        addline(x,y);
    }
    getson(1,0);
    dfs(1,0,0);
    for (i=1;i<=n;i++) printf("%lld ",ans[i]);
}

普及向:

优秀的学长学来的新姿势

什么是dsu on tree

dsu on tree用来解决这样一类问题:统计树上一个节点的子树中具有某种特征的节点数。
例如子树中颜色为x的个数。
这种方法可以做到O(nlogn)的复杂度。
那么dsu到底是个什么玩意呢?其实它的中文译名就是众所周知的并查集…
有的小朋友就会问了,并查集怎么跑到树上去的呢?
恩……其实说白了就是启发式合并:在做一类维护问题的时候,将size较小的合并到较大的size上,从而达到降低时间复杂度的目的。
又是并查集的按秩合并思想应用

一个例子

以上面的问题举个例子。

暴力
void add(int x,int fa,int vv)
{
    cnt[a[x]]+=vv;
    if (cnt[a[x]]>maxx) sum=(LL)a[x],maxx=cnt[a[x]];
    else if (cnt[a[x]]==maxx) sum+=(LL)a[x];
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa) add(v[i],x,vv);
}
void dfs(int x,int fa,int k)
{
    sum=0,maxx=0;
    add(x,fa,1);
    ans[x]=sum;
    add(x,fa,-1);
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa) dfs(v[i],x,0); 
}

代码简短也会T
在这种做法中,每次统计x节点前,暴力将x的子树的贡献加入,统计结束后,再暴力删除贡献,消除影响。
时间复杂度O(n^2)
但是这样有很多无用的删除操作,能不能减少这种操作呢。

树链剖分
void getson(int x,int fa)
{
    size[x]=1;
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa)
      {
        getson(v[i],x);
        if (size[v[i]]>size[son[x]]) son[x]=v[i];
        size[x]+=size[v[i]];
      }
}
void add(int x,int fa,int vv)
{
    cnt[a[x]]+=vv;
    if (cnt[a[x]]>maxx) sum=(LL)a[x],maxx=cnt[a[x]];
    else if (cnt[a[x]]==maxx) sum+=(LL)a[x];
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa && v[i]!=Son) add(v[i],x,vv);
}
void dfs(int x,int fa,int k)
{
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa && v[i]!=son[x]) dfs(v[i],x,0); 
    if (son[x])//不是叶子 
      dfs(son[x],x,1),Son=son[x];
    add(x,fa,1);Son=0;
    ans[x]=sum;
    if (!k) add(x,fa,-1),maxx=sum=0;//轻儿子 
}

在这种做法中,我们先进行树链剖分。
dfs的时候,首先dfs节点x的轻儿子,暴力消去影响,再dfs节点x的下一个轻儿子,一次类推。
然后dfs节点x的重儿子,无需消去影响。
在最后,我们为了统计x,再将x轻儿子的贡献加回来。
看起来很暴力,但是实际上它的时间复杂度是O(nlogn)的,跑得飞快。

证明:只有dfs到轻边时,才会将轻边的子树中合并到上一级的重链,树链剖分将一棵树分割成了不超过logn条重链。
每一个节点最多向上合并logn次,单次修改复杂度O(1)。
所以整体复杂度是O(nlogn)的。

dfs序莫队

一个子树中的节点在dfs序中是连续的,所以可以通过dfs序,将子树问题转化为序列问题,这样就可以跑莫队了。
时间复杂度O(q√n)

dfs序主席树

还可以通过dfs序建出主席树,查询就是差分后的单点查询。
时间复杂度O((n+q)logn) 但是空间复杂度是O(nlogn)的

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值