P3899 [湖南集训]谈笑风生 主席树

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#include<math.h>
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define LL long long
using namespace std;
const int maxx = 3e5+6;
struct node{
  int l,r;
  LL val;
}tree[maxx<<5];
int ver[maxx*2],Next[maxx*2],head[maxx];
///邻接表
int deapth[maxx],Size[maxx],dfn[maxx],root[maxx];
///深度数组 包含自己在内的子节点数目 DFS序 主席树的根
int tot,order,deep,cnt;
///边的序号 DFS序标记 最深深度
void add(int x,int y){
   ver[++tot]=y;Next[tot]=head[x];head[x]=tot;
   ver[++tot]=x;Next[tot]=head[y];head[y]=tot;
}
void inserts(int l,int r,int pre,int &now,int pos,LL val){
     ///动态开点部分
     now=++cnt;
     ///把旧的节点的信息更新到新的节点上
     tree[now]=tree[pre];
     ///维护前缀和
     tree[now].val+=val;
     if (l==r){
        return;
     }
     int mid=(l+r)>>1;
     if (pos<=mid){
        inserts(l,mid,tree[pre].l,tree[now].l,pos,val);
     }else {
        inserts(mid+1,r,tree[pre].r,tree[now].r,pos,val);
     }
}
LL query(int L,int R,int l,int r,int ql,int qr){
     LL ans=0;
     ///如果在范围内,直接返回
     if (ql<=l && r<=qr){
        return tree[R].val-tree[L].val;
     }
     int mid=(l+r)>>1;
     if(ql<=mid)ans+=query(tree[L].l,tree[R].l,l,mid,ql,qr);
     if(qr>mid)ans+=query(tree[L].r,tree[R].r,mid+1,r,ql,qr);
      return ans;
}
void dfs1(int x,int fa){
     Size[x]=1;
     deapth[x]=deapth[fa]+1;
     ///更新最深是深度
     deep=max(deep,deapth[x]);
     for(int i=head[x];i;i=Next[i]){
        if(ver[i]==fa)continue;
        dfs1(ver[i],x);
        Size[x]+=Size[ver[i]];
     }
}
void dfs2(int x,int fa){
     dfn[x]+=++order;
     ///这里注意,由于我们需要按照树的变量顺序来建立序列,所以应该是root[order-1],root[order]
     ///size减1是为了减去自己
     inserts(1,deep,root[order-1],root[order],deapth[x],Size[x]-1);
     for (int i=head[x];i;i=Next[i]){
        if(ver[i]==fa)continue;
        dfs2(ver[i],x);
     }
}
int main(){
  int n,q;
  int uu,vv,l,r;
  while(~scanf("%d%d",&n,&q)){
      order=0;
      deep=0;
      cnt=0;
      memset(Next,0,sizeof(Next));
      memset(Size,0,sizeof(Size));
      memset(dfn,0,sizeof(dfn));
      memset(deapth,0,sizeof(deapth));
      rep(i,1,n-1){
         scanf("%d%d",&uu,&vv);
         add(uu,vv);
      }
      dfs1(1,0);
      dfs2(1,0);
      LL ans=0;
      int p,k;
      while(q--){
         scanf("%d%d",&p,&k);
         ans=0;
         ///当a点在b点的的下面,那么b所处于的位置,应该是其a的深度和k当中小的那个
         ///因为b只会在a的上面k个位置,并且如果a的深度太小的话,其深度可能达不到k个
         ///C的位置的可能一定是a的子节点
         ans+=(LL)(Size[p]-1)*min(k,deapth[p]-1);
         ///当b点在a的下端,那么c应该在b的子节点中
         ///所以我们要查询a也就是p的子节点中 所以第一个范围oot[dfn[p]-1],root[dfn[p]+Size[p]-1]
         ///查询其子节点中深度在deepth[p]+1到death[p]+k的节点中子节点的个数和
         ///因为我们构架线段树的时候,构建的是前缀和
         ans+=query(root[dfn[p]-1],root[dfn[p]+Size[p]-1],1,deep,deapth[p]+1,min(deapth[p]+k,deep));
         if (deapth[p]==deep)ans=0;
         printf("%lld\n",ans);
      }
  }
  return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值