题意:
给n个点的树,定义从x点向上走k步到达的节点为x的 k层祖先。
m次查询,给出x k,问有多少个点与x具有相同的 k层祖先。
思路:
先求出x的k层祖先fa,题目转化为求以fa为根的子树下深度与x相同的节点个数,减去x本身的一个就是答案了
x的k层祖先可以用树上倍增解决
离线储存询问,问题就变成离线子树问题,用dsu on tree解决,
询问储存的方式是:二元组存储问题id和x的层数,
每当统计完根为fa的子树之后,遍历以fa为根的询问,然后更新答案,具体看代码
cal函数计算的是以当前点为根的子树中,各个深度的点的个数
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e5+5;
const int maxd=15;
vector<int>g[maxm];
vector<pair<int,int> >q[maxm];
int f[maxm][20],d[maxm];
int sz[maxm],son[maxm];
int mark[maxm];
int cnt[maxm];
int res[maxm];
int n,m;
void dfs(int x,int fa){
//倍增
for(int i=1;i<=maxd;i++){
if(!f[x][i-1])break;
f[x][i]=f[f[x][i-1]][i-1];
}
//树形dp求轻重儿子
sz[x]=1;
son[x]=-1;
for(int v:g[x]){
if(v==fa)continue;
f[v][0]=x;
d[v]=d[x]+1;
dfs(v,x);
sz[x]+=sz[v];
if(son[x]==-1||sz[son[x]]<sz[v]){
son[x]=v;
}
}
}
int getfa(int x,int dep){//求深度为dep的x的祖先
for(int i=maxd;i>=0;i--){
if(d[f[x][i]]>=dep){
x=f[x][i];
}
}
return x;
}
void cal(int x,int fa,int change){
cnt[d[x]]+=change;
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]-1;
}
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++){
int x;
scanf("%d",&x);
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<=0){//如果向上走越界了
res[i]=0;
continue;
}
int fa=getfa(x,d[x]-k);//求祖先
q[fa].push_back(make_pair(i,d[x]));//询问id和深度d[x]的二元组询问存入q[fa]中
}
solve(0,0,1);
for(int i=1;i<=m;i++){
printf("%d ",res[i]);
}
puts("");
return 0;
}