[CF600E] Lomsat Gelral [树链剖分/树上启发式合并]

题意:
给出一个有 N N N个点,以 1 1 1号点为根的有根树。每个点有一种颜色 c i ≤ N c_i\le N ciN
以某个点为根的子树中,如果一种颜色出现的次数不比其它颜色少,称它是这个点的支配颜色。
点的支配颜色的和,是指,某个点的所有支配颜色的编号的和。
求这棵树上每个点的支配颜色的和。
N ≤ 1 0 5 。 N\le10^5。 N105

简单地考虑:

可以暴力统计每个点,每种颜色的出现次数。 Θ ( N 2 ) \Theta(N^2) Θ(N2)

这种做法显然会爆炸,也显然有很大的优化空间。

我们知道到某个点的 A n s Ans Ans等于它的所有儿子的 A n s Ans Ans的和,所以儿子的信息不能白白浪费。

d f s dfs dfs,然后返回的时候把子树的贡献加到父亲上?
虽然好像少了删除的步骤,但是实际上,
不仅爆了空间(存每个点的答案一共 Θ ( N 2 ) \Theta(N^2) Θ(N2)),也爆了时间(扫一遍加贡献一共 Θ ( N 2 ) \Theta(N^2) Θ(N2))。

既然没法完全保存儿子的信息,再加上只能存储 Θ ( N ) \Theta(N) Θ(N)级别的信息(每个颜色出现次数)

那么顶多就把一个儿子的信息带上来,其它儿子只能暴力了。显然带上重儿子最优。

这样能够减少多少开销?

D F S DFS DFS。访问某一个点的时候,
我们先把它的轻儿子都逐个递归下去 s o l v e solve solve,然后再解决重儿子。
统计完一个轻儿子要把它的信息抛弃掉,再统计下一个
而统计完重儿子,这个点的所有儿子也都有答案了。
可以留下重儿子的信息,再暴力加上轻儿子的信息和正在访问的点本身的信息,得出这个点的答案。

怎么分析复杂度?很明显我们需要知道的就是某一个点会被暴力统计多少次

已经有了轻重儿子的概念了,干脆按照树链剖分的思路来分析
轻边 ( u , v ) (u,v) (u,v) s i z [ v ] ≤ s i z [ u ] 2 siz[v]\le \frac{siz[u]}{2} siz[v]2siz[u]
并且由此可得一条从根向下的路径经过的轻边不会超过 l o g 2 N log_2N log2N条,
经过重链数量也不会超过轻边数量 + 1 +1 +1

某个点到根的路径上有多少条轻边,这个点就会被统计几次。

复杂度是 Θ ( N l o g N ) \Theta(NlogN) Θ(NlogN)

p s . ps. ps.暴力统计可以在 d f s dfs dfs序上也可以直接在树上暴力统计,随意了

注意 A n s Ans Ans可能爆 i n t int int

本题也可以搞树上启发式合并,每次把小的合并进大的里面;还可以用 m a p map map暴力。

#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define LL long long
#define add_edge(a,b) nxt[++tot]=head[a],head[a]=tot,to[tot]=b
int N,tot=0,mx=0;
LL tmp=0;
int ci[100005]={},nxt[200005]={},head[200005]={},to[200005]={};
int siz[100005]={},son[100005]={};
int cnt[100005]={};
LL ans[100005]={};
bool vis[100005]={}; 
void pthDec(int x,int fa)
{
	siz[x]=1;
	for(int t=1,i=head[x];i;i=nxt[i])
	{
		if(to[i]==fa)continue;
		pthDec(to[i],x); siz[x]+=siz[to[i]];
		if(siz[to[i]]>t)t=siz[to[i]],son[x]=to[i];
	}
}
void modify(int x,int fa,int delta)
{
	cnt[ci[x]]+=delta;
	if((delta==1)&&(cnt[ci[x]]>=mx))
	{
		if(cnt[ci[x]]>mx)tmp=0,mx=cnt[ci[x]];
		tmp+=ci[x];
	}
	for(int i=head[x];i;i=nxt[i])
	{
		if((to[i]==fa)||vis[to[i]])continue;
		modify(to[i],x,delta);
	}
}
void dfs(int x,int fa,bool keep)
{
	
	for(int i=head[x];i;i=nxt[i])
	{
		if(to[i]==fa||to[i]==son[x])continue;
		dfs(to[i],x,0);
	}
	
	if(son[x])dfs(son[x],x,1),vis[son[x]]=1;
	modify(x,fa,1),ans[x]=tmp;
	if(son[x])vis[son[x]]=0;
	
	if(!keep)modify(x,fa,-1),mx=tmp=0;
}
int main()
{
	scanf("%d",&N);
	for(int i=1;i<=N;++i)scanf("%d",&ci[i]);
	for(int u,v,i=1;i<N;++i)
	{
		scanf("%d%d",&u,&v);
		add_edge(u,v); add_edge(v,u);
	}
	pthDec(1,0);
	dfs(1,0,0);
	for(int i=1;i<=N;++i)printf("%I64d ",ans[i]);
	return 0;
}

你可能感兴趣的:(树链剖分)