CodeForces - 1354D Multiset(树状数组倍增找第k小)

题目链接

题目大意

  模拟一下multiset,一共有两个操作:
  1.插入一个数
  2.删除第k小的数

解题思路

  用权值树状数组来维护,主要是新学了树状数组倍增的方法,可以比二分少一个log来查找第k小的数,maxn不一定得是2的幂,但得保证一定能枚举到范围内的所有数,即枚举的最大的2的幂的2倍要比值域大。
  比如要查找第k小的数,我们倒着枚举二进制位,如果区间\([0, 2^x]\)的数的数量比k要小,那么说明第k小的数肯定是比\(2^x\)要大的,那么根据二进制的性质,这个数肯定是介于\([2^x, 2^{x+1}]\)之间的,那么我们就用一个变量res加上\(2^x\),然后k减去其中的数量。再继续枚举更低位,判断区间\([res, res+2^y]\)的数是否比k大,来继续更新res和k的值(你也可以看成是重新进行了一遍前面的操作,其原理和前面是一样的, 只不过这回枚举的2的幂的大小和k的值比之前更小了),这样可以再复杂度为log(n)的情况下找出第k小数。

const int maxn = 1<<20;
const int maxm = 2e5+10;
int n, q, c[maxn+1];
void add(int x, int y) {
    while(x<maxn) {
        c[x] += y;
        x += x&-x;
    }
}
int get_k(int x) {
    int res = 0;
    for (int i = maxn/2; i>=1; i>>=1) 
        if (c[res+i]<x) {
            res += i;
            x -= c[res];
        }
    return res+1;
}
int main(void) { 
    IOS;
    cin >> n >> q;
    for (int i = 1; i<=n; ++i) {
        int num; cin >> num;
        add(num, 1);
    }
    while(q--) {
        int num; cin >> num;
        if (num>0) add(num, 1);
        else {
            num = -num;
            int x = get_k(num);
            add(x, -1);
        }
    }
    int ans = 0;
    for (int i = 1; i<maxn; ++i)
        if (c[i]) {
            ans = i;
            break;
        }
    cout << ans << endl;
    return 0;   
}

posted @ 2021-08-17 21:48  shuitiangong  阅读(144)  评论(0编辑  收藏  举报