[Acwing244] 谜一样的牛

[Acwing244] 谜一样的牛

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

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

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

输入格式

第 1 行:输入整数 \(n\)

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

输出格式

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

\(i\)行输出第 \(i\) 头牛的身高。

数据范围

\(1≤n≤10^5\)

题解

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

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

这也是逆康托展开的基本原理。我们从最后一头牛开始确定身高,并记录可选的身高集合,一开始是\({1,2,\dots,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(N\log{N})\)
  • 空间复杂度 \(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 @ 2022-02-25 12:37  Sarfish  阅读(44)  评论(0)    收藏  举报