码迷,mamicode.com
首页 > 其他好文 > 详细

CF600E Lomsat gelral(线段树合并)

时间:2020-10-19 22:36:15      阅读:20      评论:0      收藏:0      [点我收藏+]

标签:algorithm   problems   复杂度   force   需要   lang   自己   pre   oid   

http://codeforces.com/problemset/problem/600/E

题意:给一个树,每个点有一个颜色,让你对于每个点,求以他为根的子树中,颜色是 出现数量最多的颜色 的节点,的编号和(如果有多个出现数量最多的颜色,都算),\(n\le 10^5\)

线段树合并

用到线段树合并,线段树合并大概就是将两颗线段树的信息合并到一个上
这里合并的大都是动态开点的线段树,因为如果要合并一颗满的线段树(就是每个节点都有),那直接 \(O(n)\) 新建一颗树大概就是最好的复杂度了
但如果是权值线段树等需要动态开点的,点的范围一般会非常大,\(O(n)\) 新建显然是不可能,所以下面说的这种合并是 \(O(\text{两颗要合并的树的重叠大小})\)
考虑如何把 \(b\) 合并到 \(a\)

  • 如果 \(a\)\(b\) 是空的,直接返回另一个
  • 如果 \(a\)\(b\) 已经是叶子节点了,根据题目的具体需要把 \(b\) 的信息加到 \(a\) 上,返回 \(a\)
  • 递归 \(a,b\) 的左儿子和右儿子进行合并
  • 合并 \(a\) 左右儿子的信息到 \(a\),也就是 pushup

正确性应该比较显然

关于复杂度:
从上面的步骤可以发现单次合并的复杂度是 \(O(\text{两颗要合并的树的重叠大小})\),不是 \(O(\log n)\)
那如果要合并 \(n\) 个都只有一个元素的线段树为一个有 \(n\) 个元素的线段树呢?应该是 \(O(n\log 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(线段树合并)

标签:algorithm   problems   复杂度   force   需要   lang   自己   pre   oid   

原文地址:https://www.cnblogs.com/suxxsfe/p/13839555.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!