1. 程式人生 > 其它 >「Template」整體二分

「Template」整體二分

帶修的區間第 \(k\) 小。

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define LL long long
#define rep(i, j, k) for(int i = (j); i <= (k); i ++)
#define per(i, j, k) for(int i = (j); i >= (k); i --)

const int Maxn = 1e5;

int n, m, tot;
char s[2];
int a[Maxn + 5], ans[Maxn + 5], Bit[Maxn + 5];

struct Node {
    int l, r, k, id, opt;
} q[Maxn * 3 + 5];

queue < Node > q1, q2;

namespace BIT {
    void Update (int x, int y) {
        for (int i = x; i <= n; i += i & -i) Bit[i] += y;
    }

    int Sum (int x) {
        int sum = 0;
        for (int i = x; i; i -= i & -i) sum += Bit[i];
        return sum;
    }    
}

using namespace BIT;

void Solve (int ql, int qr, int l, int r) { //當前處理的操作區間為 [ql,qr] ,值域區間為 [l,r]
    if (ql > qr) return ;
    if (l == r) { //找到答案
        rep (i, ql, qr) {
            if (q[i].opt == 0) ans[q[i].id] = l; //對於[ql,qr] 中的查詢操作更新答案
        }
        return ;
    }
    int mid = (l + r) >> 1; //二分
    rep (i, ql, qr) {
        if (q[i].opt == 0) { //屬於查詢操作
            int sum = Sum (q[i].r) - Sum (q[i].l - 1);
            //進行了[ql,i-1]操作後,[q[i].l,q[i].r](查詢的區間)中小於 mid 的數的個數
            if (sum >= q[i].k) { //若個數大於詢問的 k ,代表詢問的答案小於 mid (答案更小才能讓sum變小最終等於k),答案在[l,mid]
                q1.push (q[i]); //放入[l,mid]的陣列
            } else { //否則,答案在[mid+1,r]
                q[i].k -= sum; //減去[ql,i-1]操作中小於mid的數對答案排名產生的貢獻
                q2.push (q[i]); //放入[mid+1,r]的陣列
            }
        } else { //屬於 刪除 / 插入
            if (q[i].k <= mid) { //元素大小小於 mid
                Update (q[i].id, q[i].opt); //根據 opt 新增/刪除 其產生的貢獻
                q1.push (q[i]); //對於[l,mid]中的答案可能會產生貢獻
            }
            else {
                q2.push (q[i]); //對於[mid+1,r]中的答案可能會產生貢獻
            }
        }
    }
    int pos = ql - 1;
    while (q1.size ()) {
        Node res = q1.front ();
        if (res.opt) Update (res.id, -res.opt); //撤銷
        q[++ pos] = res; q1.pop ();
    }
    int now = pos; //以 now 為分界
    while (q2.size ()) {
        q[++ pos] = q2.front (); q2.pop ();
    }
    Solve (ql, now, l, mid);
    Solve (now + 1, qr, mid + 1, r);  //遞迴
}

int main () {
    scanf ("%d %d", &n, &m);

    rep (i, 1, n) {
        scanf ("%d", &a[i]);
        q[++ tot] = {0, 0, a[i], i, 1}; //將初始的陣列看成插入元素
    }

    rep (i, 1, m) {
        scanf ("%s", s + 1);
        switch (s[1]) {
            case 'Q' : {
                int l, r, k;
                scanf ("%d %d %d", &l, &r, &k);
                q[++ tot] = {l, r, k, i, 0}; //查詢操作
                break;
            }
            case 'C' : {
                int x, y;
                scanf ("%d %d", &x, &y);
                //將修改拆成 刪除+插入
                q[++ tot] = {0, 0, a[x], x, -1}; //刪除
                a[x] = y; //更新一下
                q[++ tot] = {0, 0, a[x], x, 1}; //插入
                break;
            }
        }
    }

    memset (ans, -1, sizeof ans);
    Solve (1, tot, -1e9, 1e9);

    rep (i, 1, m) {
        if (~ans[i])
            printf ("%d\n", ans[i]);
    }
    return 0;
}