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