树上启发式合并

近乎板子的题(但蒻蒟还是花了一整天)

有些问题求解时,处理完一个子树时,需要删除这颗子树的信息(答案可以顺便统计)再去统计其他子树,不然另一棵子树的信息会集成到新的子树中,导致统计出错

这时候为了减少时间复杂度,就要使用 DSU on tree ,即标题

主要做法是:
指定一颗子树最后 dfs , dfs 完其他子树并删除信息后 , 再来 dfs 这颗子树 ,因为这是最后一棵子树了,所以不用删除信息来为不存在的下一颗子树做准备 , 这样时间复杂度就降下来了 (为什么会降我也不知道啊 awa )

所以我们用轻重子树来区别 , 将重子树作为最后一个遍历的 , 这么做不是因为必须要用重子树 , 而是因为选它好区分(不选这个也不知道能用什么了,不知道,我是蒻蒟)

看看这题的代码
```cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=2e5+5;
int n,m,cnt,head[N],hson[N],HSON,size[N],clr[N],tclr[N],clcnt[N],ans;

struct Edge{
	int nxt,to;
}edge[N<<1];

void add(int f,int t)
{
	edge[++cnt].nxt=head[f];
	edge[cnt].to=t;
	head[f]=cnt;
}

void dfsn(int now)
{
	size[now]=1;
	for(int i=head[now];i;i=edge[i].nxt)
	{
		int to=edge[i].to;
		dfsn(to);
		size[now]+=size[to];
        //找到重子树
		if(size[to]>size[hson[now]]) hson[now]=to;
	}
}

void calcu(int now,int reserve)
{
    //根据情况,确定不同 reserve 状态的计算答案的方式
	tclr[clcnt[clr[now]]]--;
	clcnt[clr[now]]+=reserve;
	tclr[clcnt[clr[now]]]++;
    
	for(int i=head[now];i;i=edge[i].nxt)
	{
		int to=edge[i].to;
        //这里只用跳过当前节点的重子树,因为其他节点是轻子树,里面的数据没有被保留,需要重新计算,而重子树的数据被保留了,再次计算会导致错误
		if(to==HSON) continue;
		calcu(to,reserve);
	}
}

void dsu(int now,int reserve)
{
    //先 dfs 轻子树
	for(int i=head[now];i;i=edge[i].nxt)
	{
		int to=edge[i].to;
		if(to==hson[now]) continue;
		dsu(to,0);//轻子树不用保留数据
	}
	if(hson[now]) dsu(hson[now],1),HSON=hson[now];//dfs 重子树,并记录重子树是哪个节点
    //计算重子树数据
	calcu(now,1);
	HSON=0;//计算完毕后重子树节点记录清零,放置后面要删除数据的话造成数据跳过
	if(clcnt[clr[now]]*tclr[clcnt[clr[now]]]==size[now]) ans++;
	if(!reserve) calcu(now,-1);
}

int main() 
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int color,from;
		cin>>color>>from;
		add(from,i);
		clr[i]=color;
	}
	dfsn(1);
	dsu(1,1);
	cout<<ans;
	return 0;
}	

posted @ 2025-03-15 23:06  石磨豆浆  阅读(12)  评论(0)    收藏  举报