树上数颜色

先考虑暴力怎么做

dfs搜索每个节点,对于当前节点,用迭代遍历其子树并更新答案,在回溯的时候将计数数组清空,时间复杂度为 O ( n 2 ) O(n^2) O(n2)

注意到,对于每次准备回溯的时候,设当前根节点为 u u u,其父亲为 x x x,在计算 x x x的时候,如果 u u u是最后一个dfs计算的(注意此时 x x x还没有计算完成,因为我们是dfs遍历树,所以会先计算完 x x x的所有儿子),那么从 u u u回溯的时候,计数数组就不用清空了,在计算 x x x的时候,此时只用迭代遍历 x x x的除了 u u u的子树并更新答案就好了

那么我们选择的 u u u有什么特征呢?一个很自然的想法就是选择子树大小最大的,这样在迭代遍历的时候就遍历得更少了

这是对的,定义重儿子为一个节点的所有儿子中子树大小最大的那个儿子(如果有多个就随便选一个),那么 u u u就是重儿子;否则为轻儿子

如果不是很清楚上面的过程,见下面代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10;
int n,m;
int ans[N];
int c[N];
int Cnt,End[N<<1],Next[N<<1],Last[N];
int cnt[N],sz[N],L[N],R[N],visitime,Node[N],big[N];
int totcol;
void add(int x,int y)
{
	End[++Cnt]=y,Next[Cnt]=Last[x],Last[x]=Cnt;
}
void dfs0(int x,int fa)
{
	L[x]=++visitime;//L表示dfs序的起点 
	Node[visitime]=x;
	sz[x]=1;
	for(int i=Last[x];i;i=Next[i])
	{
		int u=End[i];
		if(u==fa) continue;
		dfs0(u,x);
		sz[x]+=sz[u];
		if(!big[x]||sz[big[x]]<sz[u]) big[x]=u;
	} 
	R[x]=visitime;//R表示子树dfs序的最大值,在dfs序中[L[x],R[x]]就是子树 
}
void add(int u) 
{
  	if(cnt[c[u]]==0) ++totcol;
  	cnt[c[u]]++;
}
void del(int u) 
{
  	cnt[c[u]]--;
  	if(cnt[c[u]]==0) --totcol;
}
void dfs1(int x,int fa,bool keep)//keep表示是否清空计数数组 
{
	for(int i=Last[x];i;i=Next[i])
	{
		int u=End[i];
		if(u==big[x]||u==fa) continue;
		dfs1(u,x,0);
	}
	if(big[x]) dfs1(big[x],x,1);
	for(int i=Last[x];i;i=Next[i])
	{
		int u=End[i];
		if(u==big[x]||u==fa) continue;
		for(int j=L[u];j<=R[u];j++) add(Node[j]);
	}
	add(x);
	ans[x]=totcol;
	if(!keep) 
	for(int i=L[x];i<=R[x];i++) del(Node[i]);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v),add(v,u);
	}
	for(int i=1;i<=n;i++)
	scanf("%d",&c[i]);
	dfs0(1,0);
	dfs1(1,0,0);
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		int u;
		scanf("%d",&u);
		printf("%d\n",ans[u]);
	 } 
	return 0;
} 

时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

证明:对于每个节点 u u u,考虑其对时间复杂度的贡献。显然其会在dfs的时候被遍历一次。除了dfs的这次遍历,其他时候的遍历一定是在计算 u u u到根节点的路径上的某个节点 x x x的答案的时候,由于 u u u没在 x x x的重儿子的子树内,导致被迭代遍历到了一次。我们只需要计算这样的 x x x有多少个就好了

定义轻/重边表示连接父节点与轻/重儿子的边,显然满足条件的 x x x的个数就是从 u u u到根节点的路径上轻边的个数,下面证明轻边的个数不超过 log ⁡ n \log n logn

image

那么这个与启发式合并有什么关系呢?其实就是证明过程换一种理解方式。我们从 u u u往上走,每经过一条轻边,子树大小就会至少扩大一倍,就跟启发式合并一样了,最多扩大 log ⁡ n \log n logn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值