大小接近的点对(主席树)

题目描述

一天,Chika 对大小接近的点对产生了兴趣,她想搞明白这个问题的树上版本,你能帮助她吗?Chika 会给 你一棵有根树,这棵树有 n 个结点,被编号为 1 n,1 号结点是根。每个点有一个权值,i 号结点的权值为 a[i]。如果 u 是 v 的祖先结点,并且 abs(a[u]−a[v]) ≤K,那么 (u,v) 被称作一个“** 大小接近的点对 **”。 对于树上的每个结点 i,你都需要计算以其为根的子树中的“大小接近的点对”的数量。你需要知道:  
(1) abs(x) 代表 x 的绝对值。  
(2) 每个结点都是其自身的祖先结点. 

 

 

输入

输入文件的第一行包含两个整数 n (1≤n≤105) 和 k (1≤k≤109),代表树中结点总数, 以及“大小接近的点对”的大小之差的上界。  
第二行包含 n 个整数,第 i 个整数是 a[i] (1≤ a[i] ≤109),代表 i 号结点的权值。  
第三行包含 n−1 个整数,第 i 个整数是 i+1 号结点的父结点。 
 

输出

输出应该包含n行,每一行包括一个整数。第i行的整数代表以i为根的子树中的“大小接近的点对”的数量。

样例输入 Copy

7 5
2 4 4 1 4 6 4
1 2 3 1 2 3

样例输出 Copy

19
11
5
1
1
1
1
#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
#include<cmath>
#include<stack>
#include<bitset>
using namespace std;
#define N 100010
#define LL long long

struct Node
{
    int l,r,data;
} T[N*20];

int first[N],nex[N],v[N],Ls[N],Rs[N],b[N*3],root[N];
int n,K,num,top,cnt,ans;
LL sum[N];

struct data
{
    int p,v;
} t[N*3];
bool cmp(data x,data y)
{
    return x.v<y.v;
}

void build(int pre,int cur,int p,int l,int r)//建树
{
    if(l==r)//如果是叶子节点,data为1返回
    {
        T[cur].data=T[pre].data+1;
        return;
    }
    int mid=(l+r)/2;//取区间中值进行比较
    if(p<=mid)//小于向左树查找
    {
        T[cur].l=++ans;
        T[cur].r=T[pre].r;
        build(T[pre].l,T[cur].l,p,l,mid);//重新建立一个新树
    }
    else
    {
        T[cur].r=++ans;
        T[cur].l=T[pre].l;
        build(T[pre].r,T[cur].r,p,mid+1,r);//重新建立一个新树
    }
    T[cur].data=T[T[cur].l].data+T[T[cur].r].data;//统计当前节点的元素总合
}
int Query(int L,int R,int ql,int qr,int l,int r)//区间查询
{
    if(ql<=l&&r<=qr)//当查询的区间正好在同一侧,返回
    {
        return T[R].data-T[L].data;
    }
    int mid=(l+r)/2;
    int res=0;//否则统计左右区间的和
    if(ql<=mid)
    {
        res+=Query(T[L].l,T[R].l,ql,qr,l,mid);
    }
    if(mid<qr)
    {
        res+=Query(T[L].r,T[R].r,ql,qr,mid+1,r);
    }
    return res;
}

void dfs(int x)
{
    Ls[x]=++top;
    root[Ls[x]]=++ans;
    build(root[Ls[x]-1],root[Ls[x]],b[x*3-2],1,cnt);//建树
    for(int i=first[x]; i; i=nex[i])//邻接表递归下一个点
    {
        dfs(v[i]);
        sum[x]+=sum[v[i]];
    }
    Rs[x]=top;
    sum[x]+=(LL)Query(root[Ls[x]-1],root[Rs[x]],b[x*3-1],b[x*3],1,cnt);
}
int main()
{
    int x;
    cnt=0,top=0,num=0;
    scanf("%d%d",&n,&K);
    for(int i=1; i<=n; ++i)//离散化处理
    {
        scanf("%d",&t[i*3-2].v);
        t[i*3-2].p=i*3-2;
        t[i*3-1].v=t[i*3-2].v-K;
        t[i*3-1].p=i*3-1;
        t[i*3].v=t[i*3-2].v+K;
        t[i*3].p=i*3;
    }
    sort(t+1,t+n*3+1,cmp);
    b[t[1].p]=++cnt;
    for(int i=2; i<=n*3; ++i)
    {
        if(t[i].v!=t[i-1].v)
        {
            ++cnt;
        }
        b[t[i].p]=cnt;
    }
    for(int i=2; i<=n; ++i)
    {
        scanf("%d",&x);
        v[++num]=i;
        nex[num]=first[x];
        first[x]=num;
    }
    dfs(1);
    for(int i=1; i<=n; ++i)
    {
        printf("%lld\n",sum[i]);
    }
    return 0;
}

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值