#线段树合并树上启发式合并#CF600E Lomsat gelral
Posted spare-no-effort
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#线段树合并树上启发式合并#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;
}
以上是关于#线段树合并树上启发式合并#CF600E Lomsat gelral的主要内容,如果未能解决你的问题,请参考以下文章
cf600E. Lomsat gelral(树上启发式合并)