树上启发式合并笔记

1用于离线处理不带修改的子树问题,维护子树信息

2启发式合并,将小的集合往大的集合合并,判断树上的大小集合,可以分为轻重节点,像重链剖分一样

3有两次dfs,第一次dfs获取重节点

4第二次dfs获取答案,需要遵循以下操作

(1)优先获取轻子节点及其子树答案,然后删去轻子节点对当前节点u的答案贡献

(2)获取重子节点及其子树的答案,但不删取重子节点对当前答案的贡献

(3)遍历所有轻子节点及其子树,结合u本身和重子节点,获取u的答案

5(1)和(3)不能一起做,因为会导致贡献重复计算。

6(个人理解)u节点需要结合它的子节点及其子树获取信息,需要遍历完所有子树才能获得u的答案,各子节点及其子树获取答案时互不干扰,但因为空间有限,需要共用一块计算答案的数据,因此遍历完一棵子树时,需要删掉其贡献,但是最后遍历的子树对其他子树的遍历不会有影响,所以可以保留一块子树的贡献,那么肯定保留尽量大的,获取该子树答案的同时,顺便也在获取u节点的答案

代码实现

根据模板题:Problem - 600E - Codeforces

题意:一棵n个节点的树,每个节点有各自的颜色,以1为根节点,求每个节点的子树中相同颜色最大的数量,(如果子树中两种颜色数量都是最大,两个颜色都算上,答案就是它们的和)

1数据存储

const int maxn=1e5+10;
vector<int>vv[maxn];
inline void add(int u,int v)
{
	vv[u].push_back(v);
	vv[v].push_back(u);
}
int siz[maxn],son[maxn];//记录重子节点 
int cnt[maxn];//记录当前节点子树的i的颜色的数量,这个当前节点会不断变动 
int col[maxn];//记录节点的颜色 
int flag;//标记重重子节点 
ll ans[maxn],res,maxc;//记录每个节点的答案,res表示当前的答案,maxc表示最多的颜色的数量 

2第一次dfs(获取重子节点)

void dfs1(int u,int fa)//寻找重子节点 
{
	siz[u]=1;
	for(int to:vv[u])
	{
		if(fa==to)
		continue;
		dfs1(to,u);
		siz[u]+=siz[to];
		if(siz[to]>siz[son[u]])
		son[u]=to;
	}
}

3计算贡献,获取当前答案

void count(int u,int fa,int val)//计算贡献,val为正时添加贡献,val为负时删除贡献 
{
	cnt[col[u]]+=val;
	if(cnt[col[u]]>maxc)//如果当前节点的元素数量大于maxc 
	{
		maxc=cnt[col[u]];
		res=col[u];
	}
	else if(cnt[col[u]]==maxc)//如果当前元素数量等于maxc,那么就是并列最大 
	res+=col[u];
	for(int to:vv[u])//计算子树贡献 
	{
		if(to==fa||to==flag)
		continue;
		count(to,u,val);
	}
}

4第二次dfs

void dfs2(int u,int fa,int keep)//keep代表是否要删除贡献 0为删除,1为保留 
{
	for(int to:vv[u])//先遍历轻子节点 ,同时计算轻子节点及其子树答案 
	{
		if(to==fa||to==son[u])
		continue;
		dfs2(to,u,0);
	}
	if(son[u])//遍历重子节点,同时计算重子节点及其子树答案 
	{	
		dfs2(son[u],u,1);
		flag=son[u];//标记重子节点,保证u在遍历轻子节点时不往该重子节点跑 
	}
	count(u,fa,1);//计算贡献 
	flag=0;
	ans[u]=res;//计算答案 
	if(!keep)
	{
		count(u,fa,-1);//删除贡献 
		res=maxc=0;
	}
}

5主函数

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>col[i];
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		add(u,v);
	}
	dfs1(1,0);
	dfs2(1,0,0);
	for(int i=1;i<=n;i++)
	if(i==1)
	cout<<ans[i];
	else
	cout<<' '<<ans[i]; 
	return 0;
}

6完整代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10;
vector<int>vv[maxn];
inline void add(int u,int v)
{
	vv[u].push_back(v);
	vv[v].push_back(u);
}
int siz[maxn],son[maxn];//记录重子节点 
int cnt[maxn];//记录当前节点子树的i的颜色的数量,这个当前节点会不断变动 
int col[maxn];//记录节点的颜色 
int flag;//标记重重子节点 
ll ans[maxn],res,maxc;//记录每个节点的答案,res表示当前的答案,maxc表示最多的颜色的数量 
void dfs1(int u,int fa)//寻找重子节点 
{
	siz[u]=1;
	for(int to:vv[u])
	{
		if(fa==to)
		continue;
		dfs1(to,u);
		siz[u]+=siz[to];
		if(siz[to]>siz[son[u]])
		son[u]=to;
	}
}
void count(int u,int fa,int val)//计算贡献,val为正时添加贡献,val为负时删除贡献 
{
	cnt[col[u]]+=val;
	if(cnt[col[u]]>maxc)//如果当前节点的元素数量大于maxc 
	{
		maxc=cnt[col[u]];
		res=col[u];
	}
	else if(cnt[col[u]]==maxc)//如果当前元素数量等于maxc,那么就是并列最大 
	res+=col[u];
	for(int to:vv[u])//计算子树贡献 
	{
		if(to==fa||to==flag)
		continue;
		count(to,u,val);
	}
}
void dfs2(int u,int fa,int keep)//keep代表是否要删除贡献 0为删除,1为保留 
{
	for(int to:vv[u])//先遍历轻子节点 ,同时计算轻子节点及其子树答案 
	{
		if(to==fa||to==son[u])
		continue;
		dfs2(to,u,0);
	}
	if(son[u])//遍历重子节点,同时计算重子节点及其子树答案 
	{	
		dfs2(son[u],u,1);
		flag=son[u];//标记重子节点,保证u在遍历轻子节点时不往该重子节点跑 
	}
	count(u,fa,1);//计算贡献 
	flag=0;
	ans[u]=res;//计算答案 
	if(!keep)
	{
		count(u,fa,-1);//删除贡献 
		res=maxc=0;
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>col[i];
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		add(u,v);
	}
	dfs1(1,0);
	dfs2(1,0,0);
	for(int i=1;i<=n;i++)
	if(i==1)
	cout<<ans[i];
	else
	cout<<' '<<ans[i]; 
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值