[Acwing244] 谜一样的牛

[Acwing244] 谜一样的牛

  • 逆康托展开原理 树状数组 二分查找

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

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

输入格式

第 1 行:输入整数 n

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

输出格式

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

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

数据范围

1n105

题解

一开始联想到了康托展开原理中逆康托展开将逆序对序列转换为原串的方法。因为对于最后一头牛,前面所有牛的身高状态和其自己的身高状态是n个1到n的不同的数,如果最后一头牛前方比它低的牛的个数为 k 个,而这头牛可能的高度为 1,2,,n,那么,这头牛身高至少是所有可能的高度中第k+1高的。并且它只能是第k+1高的数,下面简单地证明它。

假设这头牛的身高是第m高的 , nmk+2,那么,前方n头牛中有m1头身高小于它,有k=m1,即k+1=m,与假设nmk+2矛盾。

这也是逆康托展开的基本原理。我们从最后一头牛开始确定身高,并记录可选的身高集合,一开始是1,2,,n,如果该头牛前方身高小于它的个数为k,那么该头牛的身高确定为身高集合中第k+1大的数,然后将该身高从身高集合排除,计算下一头牛。

在逆康托展开的算法中,我们每次排除后都将可选集排序,这会造成时间复杂度过高,怎么优化呢?

这里使用一个元素全是1或0的数组:

num[N] = {0,1,1,1,1,1,1,....};

我们注意到这个数组的前缀和:

Snum[1] = 1;
Snum[2] = 2;
Snum[3] = 3;
...
Snum[n] = n;

现在我们将num[3] = 0,那么Snum[3~n]都会少1。

我们可以这样定义它:Snum[A] = B,如果num[A]!=0,那么A就是当前可选集里第B大的数。

那么,维护一个数组的前缀和并且支持单点修改的结构,当时是使用树状数组了。

  • 时间复杂度 O(NlogN)
  • 空间复杂度 O(N)

题解代码

// 树状数组

#include<iostream>
using namespace std;
const int N = 100010;
int p[N],bit[N],h[N],n;

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

//O(n)建全1树状数组
void build(){
    for(int i = 1;i<=n;i++)bit[i] = lowbit(i);
}
int query(int r){
    int ans = 0;
    while(r>0){
        ans += bit[r];
        r-= lowbit(r);
    }
    return ans;
}
void modify(int x, int k){
    while(x<=n){
        bit[x] += k;
        x += lowbit(x);
    }
}
int findKth(int kth){
    int l=1, r =n,mid;
    while(l<r){
        int mid = (l+r)/2;
        int rt =query(mid);
        if(rt<kth){
            l = mid+1;
        }
        else r = mid;
    }
    return l;
}


int main(){
    scanf("%d", &n);
    build();
    for(int i = 2;i<=n;i++)scanf("%d", p+i);
    for(int i = n;i>0;i--){
        int kth = p[i];
        /*/
        printf("BIT: ");
        for(int i = 1;i<=n;i++){
            printf("%d ", query(i)-query(i-1));
        } 
        puts("");
        /*/
        //找可选集中第k+1大的数
        h[i] = findKth(kth+1);
        //加上-1等于设置为0
        modify(h[i],-1);
    }
    for(int i = 1;i<=n;i++){
        printf("%d ",h[i]);
    }
    return 0;
}
posted @   Sarfish  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示