#线段树合并、树上启发式合并#CF600E Lomsat gelral

题目

一棵树有\(n\)个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和


分析1

线段树合并,记录\(w,sum\)分别表示编号和以及颜色和,当颜色和相同时两个编号都要加,否则只加大的那一个,时间复杂度\(O(nlog_2n)\)


代码1

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int N=100011; long long ans[N];
struct xds{int ls,rs,sum; long long w;}h[N<<5];
struct node{int y,next;}e[N<<1];
int col[N],hs[N],root[N],cnt,k=1,n;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline void print(long long ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
inline void pup(int rt){
	if (h[h[rt].ls].sum>h[h[rt].rs].sum)
	    h[rt].sum=h[h[rt].ls].sum,h[rt].w=h[h[rt].ls].w;
	else h[rt].sum=h[h[rt].rs].sum,h[rt].w=h[h[rt].rs].w;
	if (h[h[rt].ls].sum==h[h[rt].rs].sum) h[rt].w+=h[h[rt].ls].w;
}
inline void update(int &rt,int l,int r,int x){
	if (!rt) rt=++cnt;;
	if (l==r) {h[rt].w=l,++h[rt].sum; return;}
	rr int mid=(l+r)>>1;
	if (x<=mid) update(h[rt].ls,l,mid,x);
	    else update(h[rt].rs,mid+1,r,x);
	pup(rt);
}
inline void merge(int nrt,int lrt,int l,int r){
	if (l==r){
		h[nrt].w=l,h[nrt].sum+=h[lrt].sum;
		return;
	} 
	rr int mid=(l+r)>>1;
	if (h[lrt].ls){
	    if (!h[nrt].ls) h[nrt].ls=h[lrt].ls;
	        else merge(h[nrt].ls,h[lrt].ls,l,mid);
    }
	if (h[lrt].rs){
	    if (!h[nrt].rs) h[nrt].rs=h[lrt].rs;
	        else merge(h[nrt].rs,h[lrt].rs,mid+1,r);
    }
    pup(nrt);
}
inline void dfs(int x,int fa){
	for (rr int i=hs[x];i;i=e[i].next)
	if (e[i].y!=fa){
		dfs(e[i].y,x);
		merge(root[x],root[e[i].y],1,n);//合并子树
	}
	update(root[x],1,n,col[x]);//增加颜色
	ans[x]=h[root[x]].w;
}
signed main(){
    n=iut();
    for (rr int i=1;i<=n;++i) col[i]=iut(),root[i]=++cnt;//每个点构一棵线段树
    for (rr int i=1;i<n;++i){
    	rr int x=iut(),y=iut();
    	e[++k]=(node){y,hs[x]},hs[x]=k,
    	e[++k]=(node){x,hs[y]},hs[y]=k; 
	}
	dfs(1,0);
	for (rr int i=1;i<=n;++i)
	    print(ans[i]),putchar(i==n?10:32);
	return 0;
}

分析2

树上启发式合并,自底向上处理,对于子树只处理重儿子的情况,对于轻儿子统计完就清除信息,合并到父节点时才重新算一遍,除了树上数颜色,这应该是也是一道模板题吧,因为重儿子所在的子树超过子树节点的一半,所以时间复杂度应该为\(O(nlog_2n)\),树链剖分就是用了这个性质再加上线段树、树状数组的数据结构只是再多了一个\(log_2n\)


代码2

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int N=100011; long long ans[N],now;
struct node{int y,next;}e[N<<1];
int col[N],hs[N],k=1,n,mx,cnt[N],root,dep[N],fat[N],son[N],big[N];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline void print(long long ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
inline void dfs1(int x,int fa){
	dep[x]=dep[fa]+1,fat[x]=fa,son[x]=1;
	for (rr int i=hs[x],mson=-1;i;i=e[i].next)
	if (e[i].y!=fa){
		dfs1(e[i].y,x);
		son[x]+=son[e[i].y];
		if (son[e[i].y]>mson) big[x]=e[i].y,mson=son[e[i].y];//处理重儿子
	}
}
inline void update(int x,int z){//很好理解呀
	cnt[col[x]]+=z;
	if (cnt[col[x]]>mx) mx=cnt[col[x]],now=col[x];
	    else if (cnt[col[x]]==mx) now+=col[x];
	for (rr int i=hs[x];i;i=e[i].next)
	if (e[i].y!=fat[x]&&e[i].y!=root) update(e[i].y,z);
}
inline void dfs2(int x,int opt){
	for (rr int i=hs[x];i;i=e[i].next)
	if (e[i].y!=fat[x]&&e[i].y!=big[x]) dfs2(e[i].y,0);
	if (big[x]) dfs2(big[x],1),root=big[x];
	update(x,1),ans[x]=now,root=0;
	if (!opt) update(x,-1),now=mx=0;
}
signed main(){
    n=iut();
    for (rr int i=1;i<=n;++i) col[i]=iut();
    for (rr int i=1;i<n;++i){
    	rr int x=iut(),y=iut();
    	e[++k]=(node){y,hs[x]},hs[x]=k,
    	e[++k]=(node){x,hs[y]},hs[y]=k; 
	}
	dfs1(1,0),dfs2(1,0);
	for (rr int i=1;i<=n;++i)
	    print(ans[i]),putchar(i==n?10:32);
	return 0;
}
posted @ 2020-01-15 22:17  lemondinosaur  阅读(173)  评论(0编辑  收藏  举报