動態區間第k小(主席樹+線段樹套樹狀陣列)
阿新 • • 發佈:2019-01-29
靜態區間第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;
}