Dynamic Rankings(樹狀數組套權值線段樹)

Dynamic Rankings(樹狀數組套權值線段樹)

給定一個含有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,而後讀入一系列的指令,包括詢問指令和修改指令。c++

對於每個詢問指令,你必須輸出正確的回答。有兩個正整數n(1≤n≤10000),m(1≤m≤10000)。分別表示序列的長度和指令的個數。git

這道題就是一個帶修區間第k大的問題,能夠用cdq分治作,也能夠用樹套樹作。因爲有修改,若是暴力在主席樹上修改,時間複雜度就爆炸了,單個修改都是$O(nlogn)$的。再看看查詢,是$O(logn)$的,這就啓發咱們可不能夠把它們兩個平均一下,因而就用到了樹狀數組。在樹狀數組的每一個區間上都建一顆權值線段樹,維護此區間上每一個值出現的個數(鏈+權值線段樹=主席樹,樹狀數組+權值線段樹=帶修主席樹【滑稽】)。那麼修改某一個數的值,會牽動到樹狀數組上logn個區間,而後權值線段樹單點修改時間爲$O(logn)$,總時間是$O(log^2nn)$的。查詢區間$[l, r]$的第k大值,須要獲得$[1, l-1]$和$[1, r]$的權值線段樹,而後將它們相減,依然會牽動到logn個區間,因爲權值線段樹區間查詢時間是$O(logn)$,總時間也是$O(logn^2n)$的。注意要離散化,這題仍是挺難寫的。數組

因此說,數據結構能夠平衡操做的時間複雜度。數據結構

P.S. 三個月後的我看到這篇博客彷彿看到了救星(要講課啊媽蛋)spa

P.A. 四個月後的我突然發現這個東西的本質和主席樹的本質是不一樣的,由於主席樹的每棵權值線段樹是繼承與前一棵權值線段樹的,而它只是把插入和查詢的前提複雜度變成了$O(logn)$而已。不過,我仍是傾向於叫它帶修主席樹,由於它是主席樹應用到樹狀數組上的天然推論。code

#include <cctype>
#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn=2e4+5;
int n, m, a[maxn], b[maxn], cntb, rt[maxn];
int c1[maxn], c2[maxn], c3[maxn];
int size[maxn*400], cseg, ls[maxn*400], rs[maxn*400];
int tset1[maxn], tset2[maxn], cnt1, cnt2;
inline int lowbit(int x){ return x&(-x); } //樹狀數組上的權值線段樹

void ins(int &now, int l, int r, int pos, int v){
    if (!now) now=++cseg; size[now]+=v;
    if (l==r) return; int mid=(l+r)>>1;
    if (pos<=mid) ins(ls[now], l, mid, pos, v);
    else ins(rs[now], mid+1, r, pos, v);
}

void add(int x, int v){
    int k=lower_bound(b+1, b+cntb+1, a[x])-b;
    for (int i=x; i<=n; i+=lowbit(i))
        ins(rt[i], 1, cntb, k, v);
}

int query(int l, int r, int v){  //logn個權值線段樹一塊兒跳
    if (l==r) return l;
    int sum=0, mid=(l+r)>>1;
    for (int i=1; i<=cnt1; ++i) sum-=size[ls[tset1[i]]];
    for (int i=1; i<=cnt2; ++i) sum+=size[ls[tset2[i]]];
    if (v<=sum){
        for (int i=1; i<=cnt1; ++i) tset1[i]=ls[tset1[i]];
        for (int i=1; i<=cnt2; ++i) tset2[i]=ls[tset2[i]];
        return query(l, mid, v);
    } else {
        for (int i=1; i<=cnt1; ++i) tset1[i]=rs[tset1[i]];
        for (int i=1; i<=cnt2; ++i) tset2[i]=rs[tset2[i]];
        return query(mid+1, r, v-sum);
    }
}

inline void get(int &x){
    char c; int flag=1;
    for (; !isdigit(c=getchar()); ) if (c=='-') flag=-1;
    for (x=c-48; c=getchar(), isdigit(c); )
        x=(x<<3)+(x<<1)+c-48;
    if (flag==-1) x=-x;
}

int main(){
    get(n); get(m); char c;
    for (int i=1; i<=n; ++i) get(a[i]), b[++cntb]=a[i];
    for (int i=0; i<m; ++i){
        while (!isgraph(c=getchar()));
        get(c1[i]); get(c2[i]);
        if (c=='Q') get(c3[i]); else b[++cntb]=c2[i];
    }
    sort(b+1, b+cntb+1); cntb=unique(b+1, b+cntb+1)-b-1;
    for (int i=1; i<=n; ++i) add(i, 1);
    for (int i=0; i<m; ++i) if (c3[i]){
        cnt1=cnt2=0;  //把要查詢的區間都標出來
        for (int j=c1[i]-1; j; j-=lowbit(j)) tset1[++cnt1]=rt[j];
        for (int j=c2[i]; j; j-=lowbit(j)) tset2[++cnt2]=rt[j];
        printf("%d\n", b[query(1, cntb, c3[i])]);
    } else { add(c1[i], -1); a[c1[i]]=c2[i]; add(c1[i], 1); }
    return 0;
}
相關文章
相關標籤/搜索