1. 程式人生 > >P2617 Dynamic Rankings 動態主席樹

P2617 Dynamic Rankings 動態主席樹

比較 樹狀 遞歸 統計 cpp resize bits 每次 題解

\(\color{#0066ff}{ 題目描述 }\)

給定一個含有n個數的序列a[1],a[2],a[3]……a[n],程序必須回答這樣的詢問:對於給定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的數是多少(1≤k≤j-i+1),並且,你可以改變一些a[i]的值,改變後,程序還能針對改變後的a繼續回答上面的問題。你需要編一個這樣的程序,從輸入文件中讀入序列a,然後讀入一系列的指令,包括詢問指令和修改指令。

對於每一個詢問指令,你必須輸出正確的回答。

\(\color{#0066ff}{輸入格式}\)

第一行有兩個正整數n(1≤n≤100000),m(1≤m≤100000)。分別表示序列的長度和指令的個數。

第二行有n個數,表示a[1],a[2]……a[n],這些數都小於10^9。接下來的m行描述每條指令,每行的格式是下面兩種格式中的一種。 Q i j k 或者 C i t

  • Q i j k (i,j,k是數字,1≤i≤j≤n, 1≤k≤j-i+1)表示詢問指令,詢問a[i],a[i+1]……a[j]中第k小的數。
  • C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改變成為t。

\(\color{#0066ff}{輸出格式}\)

對於每一次詢問,你都需要輸出他的答案,每一個輸出占單獨的一行。

\(\color{#0066ff}{輸入樣例}\)

5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3

\(\color{#0066ff}{輸出樣例}\)

3
6

\(\color{#0066ff}{數據範圍與提示}\)

10%的數據中,m,n≤100;

20%的數據中,m,n≤1000;

50%的數據中,m,n≤10000。

對於所有數據,m,n≤100000

請註意常數優化,但寫法正常的整體二分和樹套樹都可以以大約1000ms每個點的時間過。

\(\color{#0066ff}{題解}\)

整體二分,答案值域與各種操作與詢問區間

把所有操作,詢問,甚至是輸入的初始值,全部存起來(按時間順序,輸入在最前面)

每次取當前答案範圍中點mid,用比較優秀的復雜度判斷當前問題區間每個問題到底應該屬於左區間還是右區間,並計算貢獻,遞歸下去

當答案值域區間為一個值的時候,那麽當前問題區間的所有問題的答案就是這個值

#include<bits/stdc++.h>
#define LL long long
LL in() {
    char ch; LL x = 0, f = 1;
    while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    return x * f;
}
const int maxn = 4e5 + 10;
struct node {  //定義五元組進行操作
    int tp, id, l, r, k;
    node(int tp = 0, int id = 0, int l = 0, int r = 0, int k = 0): tp(tp), id(id), l(l), r(r), k(k) {}
}q[maxn], ql[maxn], qr[maxn];
struct Tree {
protected:
    int st[maxn];
    int low(int x) { return x & (-x); }
    int len;
public:
    void resize(int n) { len = n; }
    void add(int pos, int k) { while(pos <= len) st[pos] += k, pos += low(pos); }
    int query(int pos) { int re = 0; while(pos) re += st[pos], pos -= low(pos); return re; }
}s;
int n, m, num, a[maxn], ans[maxn];
char getch() {
    char ch;
    while(!isalpha(ch = getchar()));
    return ch;
}
void work(int l, int r, int nl, int nr) {
    if(l > r || nl > nr) return;  //不合法直接返回
    if(l == r) {                  //邊界,得出ans
        for(int i = nl; i <= nr; i++) if(q[i].tp) ans[q[i].id] = l;
        return;
    }
    int mid = (l + r) >> 1, cntl = 0, cntr = 0;  //當前答案mid,放入左區間的操作個數,放入右區間的操作個數
    for(int i = nl; i <= nr; i++) {
        if(q[i].tp) {  //是詢問
            int tot = s.query(q[i].r) - s.query(q[i].l - 1);  //算出詢問區間中小於等於mid的數的個數
            if(q[i].k <= tot) ql[++cntl] = q[i];   //這些數中包括第k小,於是向左
            else q[i].k -= tot, qr[++cntr] = q[i];  //否則減去小於等於mid的貢獻,向右
        }
        else {
            if(q[i].r <= mid) s.add(q[i].l, q[i].k), ql[++cntl] = q[i]; //小於等於mid,向左,還要放在樹狀數組上統計貢獻(樹狀數組上的是小於等於mid的數的個數)
            else qr[++cntr] = q[i];    //否則放右邊
        }
    }
    for(int i = nl; i <= nr; i++) if(!q[i].tp && q[i].r <= mid) s.add(q[i].l, -q[i].k); //樹狀數組清零
    for(int i = 1; i <= cntl; i++) q[nl + i - 1] = ql[i]; //左邊的給左邊
    for(int i = 1; i <= cntr; i++) q[nl + cntl + i - 1] = qr[i];  //右邊的給右邊
    work(l, mid, nl, nl + cntl - 1), work(mid + 1, r, nl + cntl, nr);  //分別遞歸
}

int main() {
    n = in(), m = in();
    int id = 0;
    for(int i = 1; i <= n; i++) q[++num] = node(0, 0, i, a[i] = in(), 1);
    int l, r, k;
    for(int i = 1; i <= m; i++) {
        if(getch() == 'Q') {
            l = in(), r = in(), k = in();
            q[++num] = node(1, ++id, l, r, k);
        }
        else {
            l = in(), k = in();
            q[++num] = node(0, 0, l, a[l], -1);
            q[++num] = node(0, 0, l, a[l] = k, 1);
        }
    }
    s.resize(n);
    work(0, 1e9, 1, num);
    for(int i = 1; i <= id; i++) printf("%d\n", ans[i]);
    return 0;
}

P2617 Dynamic Rankings 動態主席樹