树状数组 - 谜一样的牛

树状数组 - 谜一样的牛

题目:

有n头奶牛,已知它们的身高为 1~n 且各不相同,但不知道每头奶牛的具体身高。

现在这n头奶牛站成一列,已知第i头牛前面有Ai头牛比它低,求每头奶牛的身高。

输入格式
第1行:输入整数n。

第2…n行:每行输入一个整数Ai,第i行表示第i头牛前面有Ai头牛比它低。
(注意:因为第1头牛前面没有牛,所以并没有将它列出)

输出格式
输出包含n行,每行输出一个整数表示牛的身高。

第i行输出第i头牛的身高。

数据范围
1≤n≤105

输入样例:
5
1
2
1
0
输出样例:
2
4
5
3
1

题解:

给 定 的 第 2 到 n 头 牛 的 信 息 , 首 先 可 以 确 定 的 就 是 最 后 一 头 的 身 高 。 假 设 第 n 头 牛 的 信 息 是 a n , 即 前 n − 1 头 牛 当 中 , 比 第 n 头 牛 高 的 共 有 a n 头 , 即 第 n 头 牛 是 第 a n + 1 高 的 , n 头 牛 的 身 高 分 别 是 前 n 大 的 自 然 数 , 第 n 头 牛 的 身 高 就 是 就 是 第 a n + 1 大 的 自 然 数 。   以 此 类 推 , 第 n − 1 头 牛 的 身 高 就 是 剩 下 的 n − 1 个 自 然 数 当 中 第 a n − 1 + 1 大 的 自 然 数 。   可 见 , 我 们 每 次 都 需 要 看 还 剩 下 多 少 自 然 数 没 有 被 选 过 , 在 剩 下 的 这 些 数 当 中 再 选 择 第 k 大 的 。 因 此 , 可 以 用 树 状 数 组 来 统 计 剩 下 的 自 然 数 的 个 数 。 把 每 个 自 然 数 初 始 化 标 记 为 1 , 每 选 择 一 个 自 然 数 , 就 将 其 标 记 为 0 , 求 [ 1 , x ] 的 前 缀 和 即 求 前 x 个 自 然 数 中 剩 下 的 自 然 数 的 个 数 。   查 找 剩 下 的 自 然 数 中 第 k 大 的 自 然 数 可 以 二 分 , 找 到 第 一 个 前 缀 和 等 于 k 的 位 置 , 这 个 下 标 就 是 剩 下 的 第 k 大 的 自 然 数 。 给定的第2到n头牛的信息,首先可以确定的就是最后一头的身高。\\假设第n头牛的信息是a_n,即前n-1头牛当中,比第n头牛高的共有a_n头,即第n头牛是第a_n+1高的,\\n头牛的身高分别是前n大的自然数,第n头牛的身高就是就是第a_n+1大的自然数。\\\ \\以此类推,第n-1头牛的身高就是剩下的n-1个自然数当中第a_{n-1}+1大的自然数。\\\ \\可见,我们每次都需要看还剩下多少自然数没有被选过,在剩下的这些数当中再选择第k大的。\\因此,可以用树状数组来统计剩下的自然数的个数。\\把每个自然数初始化标记为1,每选择一个自然数,就将其标记为0,\\求[1,x]的前缀和即求前x个自然数中剩下的自然数的个数。\\\ \\查找剩下的自然数中第k大的自然数可以二分,找到第一个前缀和等于k的位置,这个下标就是剩下的第k大的自然数。 2nnann1nannan+1nnnan+1 n1n1an1+1 k10[1,x]x kkk


代码:

#include <cstdio>

using namespace std;

const int N=1e5+10;

int n,a[N],tr[N],ans[N];

int lowbit(int x)
{
    return x&-x;
}

void add(int x,int c)
{
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}

int sum(int x)
{
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}

int main()
{
    scanf("%d",&n);
    for(int i=2;i<=n;i++) scanf("%d",&a[i]);
    
    for(int i=1;i<=n;i++) tr[i]=lowbit(i);   // 等价于add(i,1)
    
    for(int i=n;i;i--)
    {
        int k=a[i]+1;   ///第k小的数即身高
        int l=1,r=n;
        while(l<r)
        {
            int mid=l+r>>1;
            if(sum(mid)>=k) r=mid;
            else l=mid+1;
        }
        ans[i]=r;
        add(r,-1);
    }
    
    for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值