[CF1354D] Multiset

题目描述

要你维护一个可重集,每次操作插入一个数 \(k\), 或者删除第 \(k\) 大元素。

询问最后是否还有元素,如果有则任意输出一个元素。

正解

虽然这题直接上线段树就可以过这题,但是其实还有时空更加优秀的做法。

二分答案 \(x\),判断是否最后剩下一个小于等于 \(x\) 的元素。

把元素看成两类数,一类小于等于 \(x\),另一类大于 \(x\)

这样只需要记录两个桶 \(l_{siz}\)\(r_{siz}\) 就可以 \(O(1)\) 进行插入或者删除操作了。

时间复杂度 \(O(n \log V)\),空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;

int n, q;
int a[N], b[N];

inline int read() {
    int x = 0; bool neg = false; char ch = getchar();
    while(!isdigit(ch)) (ch == '-') && (neg = true), ch = getchar();
    while(isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
    return neg ? -x : x;
}

bool check(int x) {
    int lsiz = 0, rsiz = 0;
    for(int i = 1; i <= n; ++i)
        if(a[i] <= x) {
            ++lsiz;
        } else {
            ++rsiz;
        }
    for(int i = 1; i <= q; ++i) {
        if(b[i] < 0) {
            if(-b[i] <= lsiz) --lsiz;
            else --rsiz;
        } else {
            if(b[i] <= x) ++lsiz;
            else ++rsiz;
        }
    }
    return lsiz > 0;
}


int main() {
    n = read(), q = read();
    int rem = n;
    for(int i = 1; i <= n; ++i)
        a[i] = read();
    for(int i = 1; i <= q; ++i) {
        b[i] = read();
        if(b[i] < 0) --rem;
        else ++rem; 
    }

    if(rem <= 0) {
        puts("0");
        return 0;
    }

    int l = 1, r = n, mid, mp = n;
    while(l <= r) {
        mid = (l + r) >> 1;
        if(check(mid)) {
            if(mp > mid) mp = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }

    printf("%d\n", mp);
    return 0;
}
posted @ 2020-05-19 18:31  Lskkkno1  阅读(135)  评论(0编辑  收藏  举报