树上启发式合并(dsu on tree)

今天来讲一下树上并发式合并。

灵感来源于我在看蓝桥杯历年真题是看到的一道题目

[蓝桥杯 2023 省 A] 颜色平衡树

那么这就是一道树上并发式合并的模版

先来讲一下树上并发式合并的概念。

首先我们需要知道重儿子这个知识点重链剖分,这个是我之前写的博文

那么第零步,求出重儿子。

第一步,遍历所有轻儿子,并求出其轻儿子子树的答案,但不记录其贡献。

第二步,遍历所有重儿子,并求出其重儿子子树的答案和贡献。

第三步,从当前子树开始,计算包含当前节点以及所有轻儿子的贡献

第四步,记录答案

第五步,若当前点为轻儿子,擦除贡献

实际上,这便是树上并发式合并的模版,然而我在学习时感到十分晦涩,故我以这道例题来详细剖析这个算法。

针对这道例题,我们发现,对于每个节点u,我们用cnt[c[u]]来表示颜色的出现次数,用ccnt[cnt[c[u]]]来表示颜色出现次数为cnt[c[u]]的出现次数

什么意思呢?

以原题样例举例

对于一号节点,2号颜色出现了2次,而出现2次的种类共有3种

于是我们发现,2*3=6,正好等于其子树大小

因此经过思考,我们可以得到一个结论,那便是当cnt[c[u]]*ccnt[cnt[c[u]]]=siz[u]时,这个节点满足答案

那么我们便依照模版构思一下代码

#include<bits/stdc++.h>
using namespace std;
int c[200005],f[200005];
vector<int>g[200005];
int hson[200005];
int siz[200005];
int cnt[200005];
int ccnt[200005];
int ans=0;
int son;
void add(int x,int pt)
{
	ccnt[cnt[c[x]]]--;
	cnt[c[x]]+=pt;
	ccnt[cnt[c[x]]]++;
	for(int i=0;i<g[x].size();i++)
	{
		int v=g[x][i];
		if(v!=son)//不是重儿子
		{
			add(v,pt);
		}
	}
}
void dfs1(int x)
{
	siz[x]=1;
	for(int i=0;i<g[x].size();i++)
	{
		int v=g[x][i];
		dfs1(v);
		siz[x]+=siz[v];
		if(siz[hson[x]]<siz[v])
		{
			hson[x]=v;
		}
	}
}
void dfs2(int x,bool t)
{
	for(int i=0;i<g[x].size();i++)
	{
		int v=g[x][i];
		if(v!=hson[x])//遍历轻儿子
		{
			dfs2(v,false);
		}
	}
	if(hson[x])//查询重儿子
	{
		dfs2(hson[x],true);
	}
	son=hson[x];
	add(x,1);//从当前点开始计算包含当前点以及所有轻儿子的贡献
	if(ccnt[cnt[c[x]]]*cnt[c[x]]==siz[x])//记录答案
	{
		ans++;
	}
	son=0;//记得将重儿子标记清空
	if(t==false)
	{
		add(x,-1);//删除轻儿子贡献
	}
}
int main()
{
	int n;
	cin>>n;
	int root;
	for(int i=1;i<=n;i++)
	{
		cin>>c[i]>>f[i];
		if(f[i])
		{
			g[f[i]].push_back(i);
		}
	}
	dfs1(1);
	dfs2(1,true);
	cout<<ans<<endl;
	return 0;
}

这便是上题的正确代码

接下来来给大家再详细介绍一下这个算法的核心 

从上题我们已经了解到,当我们要看到有关树上记录颜色的题目时,我们优先考虑的就是树上启发式合并。

接下来我们来介绍另外一道比较经典的题目

题目大意为,你有一个以顶点1为根的树。每个顶点都被染上了某种颜色。如果颜色c在顶点v的子树中出现的次数不少于其他任何颜色,则称颜色c在顶点v的子树中占主导地位。因此,某个顶点的子树中可能有两个或更多的颜色占主导地位。顶点v的子树是指顶点v以及所有包含顶点v的路径到根的所有其他顶点。对于每个顶点v,找出其子树中所有主导颜色的总和

那么我们只需套用模版即可,这里我就不在打代码,大家可以自行去练习来加深理解

希望能对大家有所帮助

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值