CF600E Lomsat gelral(线段树合并)
Posted suxxsfe
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF600E Lomsat gelral(线段树合并)相关的知识,希望对你有一定的参考价值。
http://codeforces.com/problemset/problem/600/E
题意:给一个树,每个点有一个颜色,让你对于每个点,求以他为根的子树中,颜色是 出现数量最多的颜色 的节点,的编号和(如果有多个出现数量最多的颜色,都算),(nle 10^5)
线段树合并
用到线段树合并,线段树合并大概就是将两颗线段树的信息合并到一个上
这里合并的大都是动态开点的线段树,因为如果要合并一颗满的线段树(就是每个节点都有),那直接 (O(n)) 新建一颗树大概就是最好的复杂度了
但如果是权值线段树等需要动态开点的,点的范围一般会非常大,(O(n)) 新建显然是不可能,所以下面说的这种合并是 (O( ext{两颗要合并的树的重叠大小}))
考虑如何把 (b) 合并到 (a) 上
- 如果 (a) 或 (b) 是空的,直接返回另一个
- 如果 (a) 和 (b) 已经是叶子节点了,根据题目的具体需要把 (b) 的信息加到 (a) 上,返回 (a)
- 递归 (a,b) 的左儿子和右儿子进行合并
- 合并 (a) 左右儿子的信息到 (a),也就是
pushup
正确性应该比较显然
关于复杂度:
从上面的步骤可以发现单次合并的复杂度是 (O( ext{两颗要合并的树的重叠大小})),不是 (O(log n))!
那如果要合并 (n) 个都只有一个元素的线段树为一个有 (n) 个元素的线段树呢?应该是 (O(nlog n)),因为一个树一个树合并时,都只有一个元素,重叠部分就是一个 (O(log n)) 的链
所以其实当线段树值域大,但因为是动态开点,所以元素不多时,这种合并方式复杂度还是很优的
此题
对每个节点来一个动态开点的线段树,维护颜色,然后把每个节点的儿子的线段树都合并到它自己,这样此时他的线段树维护的就是整个子树的颜色信息了
线段树结构体中,cnt
是最多的颜色的出现次数,ans
是答案(编号和)
至于怎么 pushup
是线段树基本操作了(
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN puts("")
inline int read(){
register int x=0;register int y=1;
register char c=std::getchar();
while(c<‘0‘||c>‘9‘){if(c==‘-‘) y=0;c=std::getchar();}
while(c>=‘0‘&&c<=‘9‘){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
#define N 100006
#define M 200006
struct graph{
int fir[N],nex[M],to[M],tot;
inline void add(int u,int v){
to[++tot]=v;
nex[tot]=fir[u];fir[u]=tot;
}
}G;
int n;
int color[N];
struct Node{
Node *ls,*rs;
int color,cnt;
long long ans;
}dizhi[N*60],*root[N],*null=&dizhi[0];
int tot;
long long ans[N];
inline void New(Node *&a){
a=&dizhi[++tot];a->ls=a->rs=null;
}
inline void pushup(Node *tree){
if(tree->ls->cnt^tree->rs->cnt){
Node *tmp=tree->ls->cnt>tree->rs->cnt?tree->ls:tree->rs;
tree->cnt=tmp->cnt;
tree->color=tmp->color;
tree->ans=tmp->ans;
}
else{
tree->cnt=tree->ls->cnt;
tree->color=tree->ls->color;
tree->ans=tree->ls->ans+tree->rs->ans;
}
}
void change(Node *tree,int l,int r,int pos){
if(l==r){
tree->color=l;
tree->cnt++;tree->ans=l;
return;
}
int mid=(l+r)>>1;
if(pos<=mid){
if(tree->ls==null) New(tree->ls);
change(tree->ls,l,mid,pos);
}
else{
if(tree->rs==null) New(tree->rs);
change(tree->rs,mid+1,r,pos);
}
pushup(tree);
}
Node *merge(Node *a,Node *b,int l,int r){
if(a==null) return b;
if(b==null) return a;
if(l==r){
a->color=l;a->cnt+=b->cnt;
a->ans=l;
return a;
}
int mid=(l+r)>>1;
a->ls=merge(a->ls,b->ls,l,mid);
a->rs=merge(a->rs,b->rs,mid+1,r);
pushup(a);
return a;
}
void dfs(reg int u,int fa){
for(reg int v,i=G.fir[u];i;i=G.nex[i]){
v=G.to[i];
if(v==fa) continue;
dfs(v,u);
merge(root[u],root[v],1,1e5);
}
change(root[u],1,1e5,color[u]);
ans[u]=root[u]->ans;
}
int main(){
n=read();
for(reg int i=1;i<=n;i++) color[i]=read(),New(root[i]);
for(reg int u,v,i=1;i<n;i++){
u=read();v=read();
G.add(u,v);G.add(v,u);
}
dfs(1,1);
for(reg int i=1;i<=n;i++) printf("%lld ",ans[i]);
return 0;
}
其他的线段树合并:https://www.luogu.com.cn/training/3858
以上是关于CF600E Lomsat gelral(线段树合并)的主要内容,如果未能解决你的问题,请参考以下文章