1. 程式人生 > >動態區間第k小(主席樹+線段樹套樹狀陣列)

動態區間第k小(主席樹+線段樹套樹狀陣列)

靜態區間第k小問題,是給你一個序列,每次詢問序列中的一個區間中的第k小數,這個問題用普通的主席樹就可以解決。動態區間第k小問題就是在靜態的基礎上加上了修改操作,也就是每次除了詢問區間第k小之外,還可以修改序列中的某個數。因為這裡涉及到了修改操作,我們用只用主席樹好像難以完成這個問題,下面我們簡單來分析一下這個問題。
我們知道在主席樹中,第i棵線段樹T[i]維護的是序列[1,i]中的數,我們設想一下,如果我們修改一個數,那麼這個修改將影響到的線段樹有T[i], T[i + 1] … ..T[n],如果我們用普通暴力的方法,每次都更新所有的這些線段樹,那麼時間複雜度爆表,這種做法肯定是不可行的。那麼我們要想另外一種比O(n)更新還快的操作來解決這個問題。我們分析主席樹的性質可以得到,如果修改了一個樹,對那些線段樹的影響都是一樣的,比如,序列中原來位置i上的數是x,現在我們把它修改成y,那麼對於需要修改的線段樹,T[i], T[i + 1]…….T[n],每棵線段樹的影響都是對應線段樹上減去了數x,加上了數y,到了這裡我們可以聯想到樹狀陣列,好像可以優化這個操作。我們讓樹狀陣列上的每個點對應一棵線段樹,第i棵線段樹維護區間[i - lowbit(i) + 1, i]區間的變化量就行。

下面是zoj2212的ac程式碼(第一次寫資料結構套資料結構,寫的比較醜):

#include<bits/stdc++.h>
using namespace std;
const int maxn = 6e4 + 10;
int n, q;
struct node{
    int L, R;
    int sum;
    node(){
        sum = 0;
    }
}Tree[2500010];//線段樹的節點
int cnt;
int s[maxn];//樹狀陣列,每個點表示一顆線段樹的根節點
int X[maxn], Y[maxn];//輔助陣列
int T[maxn];//第i棵線段樹的根節點
int a[maxn];//原陣列 int H[maxn];//原陣列排序之後的陣列 int m;//總共不同數的個數,也就是每棵線段樹的大小 struct ask{ int l, r; int k; }Ask[maxn];//由於要整體先hash,所以必須離線處理 void init() { cnt = 0; sort(H, H + m); m = unique(H, H + m) - H; } int Hash(int x) { return lower_bound(H, H + m, x) - H; } int lowbit(int x) { return
x&(-x); } int build(int l, int r)//建立一棵空樹T[0] { int f = cnt++; Tree[f].sum = 0; if(l == r) return f; int mid = (l + r)>>1; Tree[f].L = build(l, mid); Tree[f].R = build(mid + 1, r); return f; } int insert(int pa, int x, int value, int l, int r) { int now = cnt++; Tree[now].sum = Tree[pa].sum + value; if(l == r) return now; int mid = (l + r)>>1; if(x <= mid) { Tree[now].R = Tree[pa].R; Tree[now].L = insert(Tree[pa].L, x, value, l, mid); } else { Tree[now].L = Tree[pa].L; Tree[now].R = insert(Tree[pa].R, x, value, mid + 1, r); } return now; } void update(int loc, int x, int value)//樹狀陣列更新 { for(int i = loc; i <= n; i += lowbit(i)) { s[i] = insert(s[i], x, value, 0, m - 1); } } int get(int loc, int *term)//樹狀陣列求和 { int ans = 0; for(int i = loc; i >= 1; i -= lowbit(i)) { ans += Tree[Tree[term[i]].L].sum; } return ans; } int query(int L, int R, int k, int l, int r, int *term1, int *term2, int p1, int p2) { if(l == r) return l; int mid = (l + r)>>1; int d1 = Tree[Tree[p2].L].sum - Tree[Tree[p1].L].sum; int x1 = get(R, term1); int x2 = get(L - 1, term2); int d2 = x1 - x2; int d = d1 + d2; if(k <= d) { for(int i = R; i >= 1; i -= lowbit(i)) { X[i] = Tree[X[i]].L; } for(int i = L - 1; i >= 1; i -= lowbit(i)) { Y[i] = Tree[Y[i]].L; } return query(L, R, k, l, mid, X, Y, Tree[p1].L, Tree[p2].L); } else { for(int i = R; i >= 1; i -= lowbit(i)) { X[i] = Tree[X[i]].R; } for(int i = L - 1; i >= 1; i -= lowbit(i)) { Y[i] = Tree[Y[i]].R; } return query(L, R, k - d, mid + 1, r, X, Y, Tree[p1].R, Tree[p2].R); } } int main() { //freopen("C:\\Users\\creator\\Desktop\\in.txt","r",stdin) ; //freopen("C:\\Users\\creator\\Desktop\\out.txt","w",stdout) ; int Case; scanf("%d", &Case); while(Case--) { scanf("%d%d", &n, &q); m = 0; for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); H[m++] = a[i]; } char type[10]; for(int i = 1; i <= q; i++) { scanf("%s", type); if(type[0] == 'Q') { scanf("%d%d%d", &Ask[i].l, &Ask[i].r, &Ask[i].k); } else { scanf("%d%d", &Ask[i].l, &Ask[i].r); Ask[i].k = -1; H[m++] = Ask[i].r;//後來更新的值也要hash進去 } } init(); T[0] = build(0, m - 1); for(int i = 1; i <= n; i++) { T[i] = insert(T[i - 1], Hash(a[i]), 1, 0, m - 1); } for(int i = 1; i <= n; i++) { s[i] = T[0]; } for(int i = 1; i <= q; i++) { if(Ask[i].k > 0) { for(int j = Ask[i].r; j >= 1; j -= lowbit(j)) { X[j] = s[j]; } for(int j = Ask[i].l - 1; j >= 1; j -= lowbit(j)) { Y[j] = s[j]; } printf("%d\n", H[query(Ask[i].l, Ask[i].r, Ask[i].k, 0, m - 1, X, Y, T[Ask[i].l - 1], T[Ask[i].r])]); } else { update(Ask[i].l, Hash(a[Ask[i].l]), -1); update(Ask[i].l, Hash(Ask[i].r), 1); a[Ask[i].l] = Ask[i].r; } } } return 0; }