zzuli 2520: 大小接近的点对(树状数组离散化)

2520: 大小接近的点对
时间限制: 1 Sec 内存限制: 256 MB
提交: 277 解决: 46
[提交] [状态] [讨论版] [命题人:外部导入]
题目描述
一天,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<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const int M = 500000;
typedef long long LL;
const LL mod = 20180520;
const int maxt=1000000000;
vector<int>p[N];
LL a[N], b[N], c[N], v[N];
int tot;
void add(int x)
{
    x+=1;
    while(x<N-5)
    {
        c[x]+=1;
        x+=(x&(-x));
    }
    return ;
}
LL sum(int x)
{
    LL ans=0;
    x+=1;
    while(x)
    {
        ans+=c[x];
        x-=(x&(-x));
    }
    return ans;
}
void dfs(int u,LL k)
{
    int l=lower_bound(b,b+tot,a[u-1]-k)-b-1;
    int r=upper_bound(b,b+tot,a[u-1]+k)-b-1;
    v[u]=-(sum(r)-sum(l));
    for(int it:p[u])
    {
        dfs(it,k);
        v[u]+=v[it];
    }
    int pos=lower_bound(b,b+tot,a[u-1])-b;
    add(pos);
    l=lower_bound(b,b+tot,a[u-1]-k)-b-1;
    r=upper_bound(b,b+tot,a[u-1]+k)-b-1;
    v[u]+=(sum(r)-sum(l));
}

int main()
{
    int n;
    LL k;
    while(scanf("%d %lld", &n, &k)!=EOF)
    {
        for(int i=0;i<=n;i++)p[i].clear();
        for(int i=0;i<n;i++)
        {
            scanf("%lld", &a[i]);
            b[i]=a[i];
        }
        sort(b,b+n);
        tot=unique(b,b+n)-b;
        for(int i=2;i<=n;i++)
        {
            int x;
            scanf("%d", &x);
            p[x].push_back(i);
        }
        memset(c,0,sizeof(c));
        dfs(1,k);
        for(int i=1;i<=n;i++)
            printf("%lld\n",v[i]);
    }

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值