题意:
给n个点的树,每个点有一个名字,不同的点名字可能相同
m次查询,给出x k,求以x为根的子树,深度为d[x]+k的点中有多少种不同的名字
思路:
和cf 208e差不多
离线储存询问,问题就变成离线子树问题,用dsu on tree解决,
询问储存的方式是:二元组存储问题id和x的层数,
每当统计完根为fa的子树之后,遍历以fa为根的询问,然后更新答案,具体看代码
cal函数计算的是以当前点为根的子树中,各个深度的名字数,
去重用set就行了,添加和删除分别对应set的insert和erase
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e5+5;
vector<int>g[maxm];
vector<pair<int,int> >q[maxm];
int sz[maxm],son[maxm],d[maxm];
int mark[maxm];
map<string,int>name;
int id[maxm],idx;//记录编号为i的人的名字id
set<int>cnt[maxm];//cnt[i]表示深度为i的人名数
int res[maxm];
int n,m;
void dfs(int x,int fa){
//树形dp求轻重儿子
sz[x]=1;
son[x]=-1;
for(int v:g[x]){
if(v==fa)continue;
d[v]=d[x]+1;
dfs(v,x);
sz[x]+=sz[v];
if(son[x]==-1||sz[son[x]]<sz[v]){
son[x]=v;
}
}
}
void cal(int x,int fa,int change){
if(change>0){
cnt[d[x]].insert(id[x]);
}else{
cnt[d[x]].erase(id[x]);
}
for(int v:g[x]){
if(v==fa||mark[v])continue;
cal(v,x,change);
}
}
void solve(int x,int fa,int kep){
for(int v:g[x]){//先解决轻儿子
if(v==fa||v==son[x])continue;
solve(v,x,0);
}
if(son[x]!=-1){//再解决重儿子
solve(son[x],x,1);
mark[son[x]]=1;
}
cal(x,fa,1);//计算轻儿子
for(auto i:q[x]){//遍历以x为祖先的询问,更新询问的答案
res[i.first]=cnt[i.second].size();
}
if(son[x]!=-1){
mark[son[x]]=0;
}
if(!kep){
cal(x,fa,-1);
}
}
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
char s[30];
int x;
scanf("%s%d",s,&x);
if(!name[s])name[s]=++idx;
id[i]=name[s];
g[i].push_back(x);
g[x].push_back(i);
}
dfs(0,0);
scanf("%d",&m);
for(int i=1;i<=m;i++){
int x,k;
scanf("%d%d",&x,&k);
if(d[x]+k>=maxm){//如果深度越界
res[i]=0;
continue;
}
q[x].push_back(make_pair(i,d[x]+k));//询问id和深度d[x]的二元组询问存入q[fa]中
}
solve(0,0,1);
for(int i=1;i<=m;i++){
printf("%d ",res[i]);
}
puts("");
return 0;
}