正题
题目链接:https://www.luogu.com.cn/problem/P3899
题目大意
给出nnn个点的一棵有根树,每次询问一个(p,k)(p,k)(p,k),求有多少个点对(b,c)(b,c)(b,c)满足
- ppp和bbb是ccc的祖先
- bbb与ppp的距离不超过kkk
蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤\color{white}蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤蛤
解题思路
首先如果bbb在aaa上方,那么点bbb的个数可以用深度来求,而ccc的数量就是aaa的子树大小?1-1?1
如果bbb在aaa的下方,ccc的数量就是bbb的子树大小?1-1?1,也就是对于每个bbb它的贡献是它的子树大小?1-1?1,那么就求我们在aaa的子树中与aaa距离不超过kkk的点的权值和即可。
这个可以用dfsdfsdfs序和主席树维护,时间复杂度O(nlog?n)O(n\log n)O(nlogn)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define siz(x) (ed[x]-dfn[x]+1)
using namespace std;
const ll N=3e5+10,M=6e6+10;
struct node{
ll to,next;
}a[N*2];
ll n,q,tot,cnt,D,ls[N],dep[N];
ll dfn[N],ed[N],rt[N],rfn[N];
void addl(ll x,ll y){
a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;
}
void dfs(ll x,ll fa){
dfn[x]=++cnt;rfn[cnt]=x;dep[x]=dep[fa]+1;D=max(D,dep[x]);for(ll i=ls[x];i;i=a[i].next){
ll y=a[i].to;if(y==fa)continue;dfs(y,x);}ed[x]=cnt;
}
struct Seg_Tree{
ll cnt,sum[M],ls[M],rs[M];ll Change(ll x,ll L,ll R,ll pos,ll val){
ll y=++cnt;sum[y]=sum[x]+val;if(L==R){
return y;}ll mid=(L+R)>>1;if(pos<=mid)ls[y]=Change(ls[x],L,mid,pos,val),rs[y]=rs[x];else rs[y]=Change(rs[x],mid+1,R,pos,val),ls[y]=ls[x];return y;}ll Ask(ll x,ll y,ll L,ll R,ll l,ll r){
if(!(sum[y]-sum[x]))return 0;if(L==l&&R==r)return sum[y]-sum[x];ll mid=(L+R)>>1;if(r<=mid)return Ask(ls[x],ls[y],L,mid,l,r);if(l>mid)return Ask(rs[x],rs[y],mid+1,R,l,r);return Ask(ls[x],ls[y],L,mid,l,mid)+Ask(rs[x],rs[y],mid+1,R,mid+1,r);}
}T;
int main()
{
scanf("%lld%lld",&n,&q);for(ll i=1;i<n;i++){
ll x,y;scanf("%lld%lld",&x,&y);addl(x,y);addl(y,x);}dfs(1,0);for(ll i=1;i<=n;i++){
int x=rfn[i];rt[i]=T.Change(rt[i-1],1,D,dep[x],siz(x)-1);}while(q--){
ll p,k;scanf("%lld%lld",&p,&k);ll ans=min(k,dep[p]-1)*(siz(p)-1);printf("%lld\n",ans+T.Ask(rt[dfn[p]],rt[ed[p]],1,D,dep[p]+1,dep[p]+k));}
}