AcWing 244. 谜一样的牛(树状数组+二分查找)

在这里插入图片描述
在这里插入图片描述

题意

微信图片_20220214111014.png

思路

我们用h[]存推理出所有牛的身高,a[i]表示当前第i头牛左边有多少牛比较低,c[]表示树状数组。

我们发现,如果说 i头牛的左边有a[i]头牛比它矮,那么它的身高h[i]要满足两个条件:

  • ①在数值1~n中排行a[i] + 1

  • ②没有在h[i+1],h[i+2],…,h[n]中 出现过。

  • 由于我们采用倒序遍历,h[i+1]~h[n]这些第i头牛右边所有牛的身高已确定,当然不能选了

大体流程

我们倒序遍历每个数a[i]

  • ①每次从剩余的数中找到第k=a[i]+1小的数(这是上方我们找到的规律)。

  • 删除这个数。

分析

本来执行上述两个步骤就已经可以求出答案了,但是观察数据范围:n<=1e5步骤①中由于遍历每一个数的时候都要从剩下的数中查找目标值,需要O(n^2)级别的复杂度,我们显然要加以优化,考虑利用树状数组加二分来优化。

先让a[1]~a[n]都为1,表示 这个身高我们还可以用一次,使用树状数组cO(logn)的时间复杂度内维护a[1]~a[n]前缀和(树状数组都是用来维护前缀和的),如果要删除某个数直接在这个位置上减1即可(单点修改,也是O(logn),add(x, -1)

在本题中,ask(x)表示区间1~x剩余了多少个数,如果我们想在剩余的ask(x)个数中找到第k小的数,应该要寻找的是最小的x,使得ask(x)=k

即步骤②等价于寻找一个最小的x,使得ask(x)=k。(ask(x)<k表示1~x中不足k个数,肯定是不行的)

由于ask(x)显然是单调的,因此上述的寻找过程我们可以用二分来求。

总结一下:也就是说,我们要实时维护一个01序列,支持查询第k1的位置二分查找左边界),以及修改序列中的一个数值

01序列不断在变化,因此二分查找的左右边界始终包含了整个1~n的区域)

时间复杂度 :

O(n(logn^2))(树状数组和二分常数都非常小,可能比平衡树还要快)

代码:(代码细节都体现在了上述的题解中)

#include<bits/stdc++.h>

using namespace std;
const int N = 1e5+10;
int h[N], a[N], c[N];
int n;

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

int ask(int x)
{
        int ans = 0;
        while(x) {ans+=c[x], x-=lowbit(x);}
        return ans;
}

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

int main()
{
        cin>>n;
        add(1, 1);
        for(int i=2;i<=n;++i)
        {
                scanf("%d", &a[i]);
                add(i, 1);
        }

        for(int i=n; i>=1; --i)
        {
                int k = a[i]+1;
                int l = 1, r = n;//01序列不断在变化,因此二分查找的左右边界始终包含了整个1~n的区域
                while(l<r)
                {
                        int mid = l+r>>1;
                        if(ask(mid)>=k) r = mid;
                        else l = mid + 1;
                }
                h[i] = l;
                add(l, -1);
        }

        for(int i=1;i<=n;++i) printf("%d\n", h[i]);

        return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值