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

Template - 「整體二分」

寫的簡單。主要是留給自己做複習資料。


「BZOJ1901」Dynamic Rankings.

給定一個含有 \(n\) 個數的序列 \(a_1,a_2 \dots a_n\),需要支援兩種操作:

  • Q l r k 表示查詢下標在區間 \([l,r]\) 中的第 \(k\) 小的數。
  • C x y 表示將 \(a_x\) 改為 \(y\)

引入整體二分。

其實就是我們對於二分到的一個值域 mid,去離線針對所有的詢問進行 check。

具體是利用一些資料結構。

然後根據 check 和 mid 的大小將詢問分為兩撥,再分治下去解決問題。

參考於 OI-wiki 的板子很精簡且思路便於理解。

#include <cstdio>

int Abs (int x) { return x < 0 ? -x : x; }
int Max (int x, int y) { return x > y ? x : y; }
int Min (int x, int y) { return x < y ? x : y; }

int Read () {
    int x = 0, k = 1;
    char s = getchar();
    while (s < '0' || s > '9') {
        if(s == '-')
            k = -1;
        s = getchar ();
    } 
    while ('0' <= s && s <= '9') 
        x = (x << 3) + (x << 1) + (s ^ 48), s = getchar ();
    return x * k;
}

void Write (int x) {
    if(x < 0) 
        x = -x, putchar ('-');
    if(x > 9)
        Write (x / 10);
    putchar (x % 10 + '0');
}

void Print (int x, char s) { Write (x), putchar (s); }

const int MAXN = 2e5 + 5;

char Opt[3];
int Ans[MAXN], BIT[MAXN], a[MAXN], n, m;

int Low_Bit (int x) {
    return x & -x;
}

void Update (int k, int x) {
    for (int i = k; i <= n; i += Low_Bit (i))
        BIT[i] += x;
}

int Query (int k) {
    int Res = 0;
    for (int i = k; i >= 1; i -= Low_Bit (i))
        Res += BIT[i];
    return Res;
}

struct Node {
    bool Type;
    int Id, x, y, k;
    Node () {}
    Node (bool T, int I, int X, int Y, int K) {
        Type = T, Id = I, x = X, y = Y, k = K;
    }
} q[MAXN], Tmp[2][MAXN];

void Solve (int l, int r, int L, int R) {
    if (l > r || L > R)
        return ;
    if (L == R) {
        for (int i = l; i <= r; i++)
            if (q[i].Type)
                Ans[q[i].Id] = L;
        return ;
    }
    int Mid = (L + R) >> 1, Cnt0 = 0, Cnt1 = 0;
    for (int i = l; i <= r; i++) 
        if (q[i].Type) {
            int Check = Query (q[i].y) - Query (q[i].x - 1);
            if (q[i].k <= Check)
                Tmp[0][++Cnt0] = q[i];
            else 
                q[i].k -= Check, Tmp[1][++Cnt1] = q[i];
        }
        else {
            if (q[i].y <= Mid)
                Update (q[i].x, q[i].k), Tmp[0][++Cnt0] = q[i];
            else 
                Tmp[1][++Cnt1] = q[i];
        }
    for (int i = 1; i <= Cnt0; i++)
        if (!Tmp[0][i].Type)
            Update (Tmp[0][i].x, -Tmp[0][i].k);
    for (int i = 1; i <= Cnt0; i++)
        q[l + i - 1] = Tmp[0][i];
    for (int i = 1; i <= Cnt1; i++)
        q[l + Cnt0 + i - 1] = Tmp[1][i];
    Solve (l, l + Cnt0 - 1, L, Mid), Solve (l + Cnt0, r, Mid + 1, R);
}

int main () {
    n = Read (), m = Read ();
    int p = 0, Tot = 0;
    for (int i = 1, x; i <= n; i++)
        x = Read (), q[++p] = Node (0, 0, i, x, 1), a[i] = x;
    for (int i = 1, x, y; i <= m; i++) {
        scanf ("%s", Opt + 1), x = Read (), y = Read ();
        if (Opt[1] == 'Q') {
            int k = Read ();
            q[++p] = Node (1, ++Tot, x, y, k);
        }
        else 
            q[++p] = Node (0, 0, x, a[x], -1), q[++p] = Node (0, 0, x, y, 1), a[x] = y;
    }
    Solve (1, p, 0, 1e9);
    for (int i = 1; i <= Tot; i++)
        Print (Ans[i], '\n');
    return 0;
}

「ZJOI2013」K 大數查詢

\(n\) 個位置,\(m\) 個操作。操作有兩種:

  • 1 a b c 表示在第 \(a\) 個位置到第 \(b\) 個位置,每個位置加入一個數 \(c\)
  • 2 a b c 表示詢問從第 \(a\) 個位置到第 \(b\) 個位置,問第 \(c\) 大的數是多少。

放在這裡是因為發現它比上道題的唯一差別在於區間修改。

也就是說整體二分的資料結構理論上可以百搭。比如線段樹。

#include <cstdio>

typedef long long LL;
int Abs (int x) { return x < 0 ? -x : x; }
int Max (int x, int y) { return x > y ? x : y; }
int Min (int x, int y) { return x < y ? x : y; }

int Read () {
    int x = 0, k = 1;
    char s = getchar();
    while (s < '0' || s > '9') {
        if(s == '-')
            k = -1;
        s = getchar ();
    } 
    while ('0' <= s && s <= '9') 
        x = (x << 3) + (x << 1) + (s ^ 48), s = getchar ();
    return x * k;
}

LL Read_LL () {
    LL x = 0, k = 1;
    char s = getchar();
    while (s < '0' || s > '9') {
        if(s == '-')
            k = -1;
        s = getchar ();
    } 
    while ('0' <= s && s <= '9') 
        x = (x << 3) + (x << 1) + (s ^ 48), s = getchar ();
    return x * k;
}

void Write (int x) {
    if(x < 0) 
        x = -x, putchar ('-');
    if(x > 9)
        Write (x / 10);
    putchar (x % 10 + '0');
}

void Print (int x, char s) { Write (x), putchar (s); }

const int MAXN = 5e4 + 5;

int Ans[MAXN], n, m;

struct Segment_Tree {
    #define Lson p << 1
    #define Rson p << 1 | 1

    struct Segment_Node {
        int l, r;
        bool Clear;
        LL Sum, Lazy;        
        Segment_Node () {}
        Segment_Node (int L, int R, LL S, LL La, bool C) {
            l = L, r = R, Sum = S, Lazy = La, Clear = C;
        }
    } Tr[MAXN * 4];

    void Push (int p) {
        if (Tr[p].Clear) {
            Tr[Lson].Sum = Tr[Rson].Sum = 0;
            Tr[Lson].Lazy = Tr[Rson].Lazy = 0;
            Tr[Lson].Clear = Tr[Rson].Clear = true;
            Tr[p].Clear = false;
        }
        if (Tr[p].Lazy) {
            Tr[Lson].Sum += Tr[p].Lazy * (Tr[Lson].r - Tr[Lson].l + 1);
            Tr[Rson].Sum += Tr[p].Lazy * (Tr[Rson].r - Tr[Rson].l + 1);
            Tr[Lson].Lazy += Tr[p].Lazy, Tr[Rson].Lazy += Tr[p].Lazy;
            Tr[p].Lazy = 0;
        }
    }

    void Pull (int p) {
        Tr[p].Sum = Tr[Lson].Sum + Tr[Rson].Sum;
    }

    void Make_Tree (int p, int l, int r) {
        Tr[p].l = l, Tr[p].r = r;
        if (Tr[p].l == Tr[p].r)
            return ;
        int Mid = (Tr[p].l + Tr[p].r) >> 1;
        Make_Tree (Lson, l, Mid);
        Make_Tree (Rson, Mid + 1, r);
    }

    void Update (int p, int l, int r, LL x) {
        if (l <= Tr[p].l && Tr[p].r <= r)  {
            Tr[p].Sum += x * (Tr[p].r - Tr[p].l + 1), Tr[p].Lazy += x;
            return ;
        }
        Push (p);
        int Mid = (Tr[p].l + Tr[p].r) >> 1;
        if (l <= Mid)
            Update (Lson, l, r, x);
        if (r > Mid)
            Update (Rson, l, r, x);
        Pull (p);
    }

    LL Query (int p, int l, int r) {
        if (l <= Tr[p].l && Tr[p].r <= r) 
            return Tr[p].Sum;
        Push (p);
        int Mid = (Tr[p].l + Tr[p].r) >> 1;
        LL Res = 0;
        if (l <= Mid)
            Res += Query (Lson, l, r);
        if (r > Mid)
            Res += Query (Rson, l, r);
        Pull (p);
        return Res;
    }

    #undef Lson
    #undef Rson
} Seg;

struct Node {
    LL k;
    bool Type;
    int Id, x, y;
    Node () {}
    Node (bool T, int I, int X, int Y, LL K) {
        Type = T, Id = I, x = X, y = Y, k = K;
    }
} q[MAXN], Tmp[2][MAXN];

void Solve (int l, int r, int L, int R) {
    if (l > r || L > R)
        return ;
    if (L == R) {
        for (int i = l; i <= r; i++)
            if (q[i].Type)
                Ans[q[i].Id] = L;
        return ;
    }
    int Mid = (L + R) >> 1, Cnt0 = 0, Cnt1 = 0;
    Seg.Tr[1].Clear = true, Seg.Tr[1].Sum = Seg.Tr[1].Lazy = 0;
    for (int i = l; i <= r; i++)
        if (q[i].Type) {
            LL Check = Seg.Query(1, q[i].x, q[i].y);
            if (q[i].k <= Check)
                Tmp[1][++Cnt1] = q[i];
            else
                q[i].k -= Check, Tmp[0][++Cnt0] = q[i];
        }
        else {
            if (q[i].k > Mid)
                Seg.Update (1, q[i].x, q[i].y, 1ll), Tmp[1][++Cnt1] = q[i];
            else
                Tmp[0][++Cnt0] = q[i];
        }
    for (int i = 1; i <= Cnt0; i++)
        q[l + i - 1] = Tmp[0][i];
    for (int i = 1; i <= Cnt1; i++)
        q[l + Cnt0 + i - 1] = Tmp[1][i];
    Solve (l, l + Cnt0 - 1, L, Mid), Solve (l + Cnt0, r, Mid + 1, R);
}

int main () {
    LL k;    
    n = Read (), m = Read ();
    int p = 0, Tot = 0;    
    Seg.Make_Tree (1, 1, n);
    for (int i = 1, Opt, x, y; i <= m; i++) {
        Opt = Read (), x = Read (), y = Read (), k = Read_LL ();
        if (Opt == 1)
            q[++p] = Node (0, i, x, y, k);
        else
            q[++p] = Node (1, ++Tot, x, y, k);
    }
    Solve (1, p, -n, n);
    for (int i = 1; i <= Tot; i++)
        Print (Ans[i], '\n');
    return 0;
}