树状数组的应用:谜一样的牛

题目链接:https://www.acwing.com/problem/content/description/245/

题目:

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

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

输入格式

第 1 行:输入整数 n。

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

输出格式

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

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

数据范围

1≤n≤1e5

输入样例:

5
1
2
1
0

输出样例:

2
4
5
3
1

思路:

 举例:

0 1 2 1 0

我们刚开始可以使用的值为:(1,2,3,4,5)

我们从右往左去解:

i == 5时,b[i] == 0, 所以可以知道,前 1~4没有比它小的值,所以它只能是当前剩下的里面的最小的值。

所以a[5] == 1;(而可用情况由,1,2,3,4,5 变为了 2,3,4,5)

i == 4时, b[i] == 1, 所以可以知道, 前1 ~ 3有一个比它小的值, 所以它只能是当前剩下的里面倒数第二小的值。

所以a[4] == 3;(而可用的情况,由,2,3,4,5变为了,2 ,4,5)

i == 3时, b[i] == 2, 所以可以知道,前1~2有两个比它小的值,所以它只能是当前剩下的里面倒数第三个小的值。

所以a[3] == 5;(而可用的情况,由2,4,5变为了2,4)

i == 2时,b[i] == 1,所以可以知道,前1有一个比它小的值,所以它只能是剩下的里面倒数第二个小的值

所以a[2] == 4, 

a[1] == 2.

那么问题来了。如何实现呢?

我们可以使用树状数组的方式来维护我们现在还能够用的数值。

由于树状数组维护的是前缀和。 所以我们树状数组中的前缀和的含义就是 1~x中还有多少个数。

每一个数在树状数组中刚开始的时候都是1,

每使用掉一个值,那么树状数组中这个值就被-1. 使得前缀和 -1.

举例:我们要找当前剩下的数中第 k 小的数。

那么就可以使用二分的方式进行查询。

寻找树状数组中第一个 1~x区间 其和 大于等于 k。(也就是1~x中还剩下大于等于k个值,而又是第一个大于等于k的,其实就是第一个和为k的情况, 那么x的含义不就是当前1~n中剩下的第k小的数了嘛。)

 所以可以通过树状数组 加 二分 的方式 实现。

代码实现:

# include <iostream>
using namespace std;

const int N = 2e5 + 10;

int h[N];
int a[N];
int c[N];

int n;

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

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

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

int main()
{
    scanf("%d",&n);
    h[1] = 0;
    for(int i = 2 ; i <= n ; i++)
    {
        scanf("%d",&h[i]);
    }
    for(int i = 1 ; i <= n ; i++)
    {
        add(i,1);  // 每个i值都可以用一次
    }
    
    for(int i = n ; i >= 1 ; i--)
    {
        int t = h[i] + 1 ; // i的前面有h[i]个比它小的,所以它是当前还剩下值的第h[i] + 1小的值
        
        int l = 1 , r = n;
        
        while(l < r) // 找第一个大于等于t的值
        {
            int mid = (l + r) / 2;
            if(sum(mid) >= t)
            {
                r = mid;
            }
            else
            {
                l = mid + 1;
            }
        }
        
        a[i] = l;
        add(l,-1);  // l被用掉了。所以减1.
    }
    for(int i = 1 ; i <= n ; i++)
    {
        printf("%d\n",a[i]);
    }
    return 0;
}

 下面一道题目也可以用到树状数组的方式求解:

题目链接:https://ac.nowcoder.com/acm/contest/11253/K

题目题解:https://blog.csdn.net/qq_49120553/article/details/118940655?spm=1001.2014.3001.5501

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值