树上启发式合并
有些问题求解时,处理完一个子树时,需要删除这颗子树的信息(答案可以顺便统计)再去统计其他子树,不然另一棵子树的信息会集成到新的子树中,导致统计出错
这时候为了减少时间复杂度,就要使用 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;
}

浙公网安备 33010602011771号