题解:P2720 小A的礼物
子树数颜色问题居然没有树上启发式合并的题解?
首先观察题面,
那么,一个点能走到的位置也就是它子树内的所有节点了。也就是说,对于每一次询问的节点,我们要求出它子树内的不同礼物种类。
对于这个问题我们有一个很优秀的解法:树上启发式合并。
顾名思义,这种方法运用了启发式的思想。首先我们有朴素算法:对于每个节点,暴力统计出子树内的所有信息。
这个时候,有人开始发扬人类智慧,想到:之前的做法每次都要清空统计的数组,但是我们可以保留其中一个子树的信息,这样就可以少统计一部分了。那么保留哪个子树呢?显然是要保留大小最大的了,也就是重链剖分里的重儿子。
可以证明,按照这样的方式,每个节点被重复统计的次数等于它到根的轻边个数,而这个数是
代码非常好写,因为这其实就是一个暴力。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+6;
int n,m,ans[N],buck[N],cnt,a[N];
vector<int> f[N];
int fa[N],dep[N],son[N],siz[N];
void dfs(int x,int y){
dep[x]=dep[y]+1; siz[x]=1;
for(int u:f[x]){
if(u==y) continue;
dfs(u,x); siz[x]+=siz[u];
if(siz[u]>siz[son[x]]) son[x]=u;
}
}
void calc(int x,int son,int sgn){ //暴力递归统计子树
if(!buck[a[x]]) cnt++;
buck[a[x]]+=sgn;
if(!buck[a[x]]) cnt--;
for(int u:f[x]){
if(u==son||u==fa[x]) continue;
calc(u,son,sgn);
}
}
void dfs2(int x,int flag){//flag表示该节点是否是重儿子,即是否要清空
for(int u:f[x]){
if(u!=fa[x]&&u!=son[x]) dfs2(u,0);
}
if(son[x]) dfs2(son[x],1);
calc(x,son[x],1);
ans[x]=cnt;//对于每个节点存下答案
if(flag) return;//如果是重儿子,则跳过清空步骤
calc(x,-1,-1);
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin>>n;
for(int i=2;i<=n;++i){
cin>>fa[i];
f[i].push_back(fa[i]);
f[fa[i]].push_back(i);
}
for(int i=1;i<=n;++i) cin>>a[i];
dfs(1,0); dfs2(1,1);
cin>>m;
for(int x,i=1;i<=m;++i){
cin>>x; cout<<ans[x]<<'\n';
}
return 0;
}
希望这篇题解能够帮到你!