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

来源:互联网 发布:淘宝有什么好的男装店 编辑:程序博客网 时间:2024/05/20 00:12

题目:

我是超链接

题意:

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

题解:

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

代码:

#include <cstdio>#include <iostream>#include <cstring>#define LL long long#define N 100005using 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
原创粉丝点击