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(线段树合并)的主要内容,如果未能解决你的问题,请参考以下文章

CF600E Lomsat gelral(线段树合并)

题解Lomsat gelral [CF600E]

「CF600E」Lomsat gelral

[CF600E]Lomsat gelral

codeforces 600E . Lomsat gelral (线段树合并)

CF600E Lomsat gelral(树上启发式合并)