The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with the query like to simply find the k-th smallest number of the given N numbers. They have developed a more powerful system such that for N numbers a[1], a[2], ..., a[N], you can ask it like: what is the k-th smallest number of a[i], a[i+1], ..., a[j]? (For some i<=j, 0<k<=j+1-i that you have given to it). More powerful, you can even change the value of some a[i], and continue to query, all the same.
Your task is to write a program for this computer, which
- Reads N numbers from the input (1 <= N <= 50,000)
- Processes M instructions of the input (1 <= M <= 10,000). These instructions include querying the k-th smallest number of a[i], a[i+1], ..., a[j] and change some a[i] to t.
Input
The first line of the input is a single number X (0 < X <= 4), the number of the test cases of the input. Then X blocks each represent a single test case.
The first line of each block contains two integers N and M, representing N numbers and M instruction. It is followed by N lines. The (i+1)-th line represents the number a[i]. Then M lines that is in the following format
Q i j k or
C i t
It represents to query the k-th number of a[i], a[i+1], ..., a[j] and change some a[i] to t, respectively. It is guaranteed that at any time of the operation. Any number a[i] is a non-negative integer that is less than 1,000,000,000.
There're NO breakline between two continuous test cases.
Output
For each querying operation, output one integer to represent the result. (i.e. the k-th smallest number of a[i], a[i+1],..., a[j])
There're NO breakline between two continuous test cases.
Sample Input
2
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
Sample Output
3
6
3
6
总算是A了第一道主席树了,主席树的总体思想就是用n颗线段树(n是数组长度,并且这n颗线段树的结构是一模一样的,都是0-num)去维护一个前缀和,对于没有修改的主席树只需要维护一个T[i] 就够了,其中T[i] 表示第i颗线段树 当前已经插入了i个数,线段树的总共num个节点,(num是数组里面的数加上修改后的数去重以后得到的长度,由于数很大,要把所有的数哈希),线段树维护的是该区间数的个数,因为线段树里面的叶子节点是从小到大排序的,所有要查找第k大的数的时候若线段树左子树数的总数小于k那么第k大的一定在右子树,然后k-=左子树数的总数查找右子树,否则就在左子树查找左子树。
现在要查找的是在l到r的第k大的数,因为第i颗线段树表示当前这颗线段树已经插入了1至i的这些数,因为l至r区间应该是一颗只插入了第l至r的数的线段树,而这颗线段树可以用
T[r] - T[l-1]表示,于是这颗树的左子树的数的总数就等于第r颗树的左子树的数的总数减去第l-1颗树的左子树的数的总数。
上述说的只是没有修改的主席树,那么如果要修改节点的话,就要用树状数组维护第1到第i颗树的数的个数总和,比如说现在要把数组中第i个位置的修改为t(假设t在num个数里面的位置为vis,原来的a[i]在numge数里面的位置是vis1)那么树状数组中从第i颗树开始到第n个数的所有线段树的所有包含vis1的段全部减1,所有包含vis的段全部加1,因为主席树是一种不修改原来结构的数据结构,它每次修改一个数的时候,相当于直接添加了一颗新的树原来的树都不变,所以在求l到r第k大的数的时候,直接还是用上述的方法求出T[r] - T[l-1]再加上 S[r] - s[l-1]。
主席树建的n颗线段树并不是每建一颗树就开一颗树那么大的空间,而是重复利用之前的树,主席树一开始是建一颗叶子节点为0-num的一颗空树T[0],当你建T[1] 的时候,首先 先为T[1]建一个跟节点,如果当前要插入的数在左区间,那么为左区间建一个节点,右区间等于T[0]的右区间,然后继续往下,一直到跟节点,反正就是当前要插入左区间,就为左区间建一个节点,右区间等于上一颗树的右区间,否则 为右区间建一个节点,左区间等于上一颗树的左区间。
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int maxn = 60005;
int T[maxn]; //主席树的n个节点
int S[maxn]; //树状数组维护主席树的前缀和
int a[maxn],b[maxn];//离线哈希
int tot;
int num;
int use[maxn];//求前缀和时保存当前线段树的要查询区间的根节点
int lson[2500000],rson[2500000],c[2500000];//保存每颗线段树的左右节点,和当前节点数的个数
int n,m;
void hase(int k) //离线哈希
{
sort(b,b+k);
num = unique(b,b+k) - b;
}
int get_hase(int now) // 获取数的位置
{
return lower_bound(b,b+num,now) - b;
}
struct Q
{
int l,r,w,kind;
}q[10005];//保存查询
int build(int l,int r)
{
int root = tot++;
c[root] = 0;
if(l != r)
{
int mid = (l + r) >> 1;
lson[root] = build(l,mid);
rson[root] = build(mid+1,r);
}
return root;
}//建树
int insert1(int root,int pos,int val) // 创建一个新线段树,之前的继续保留下来
{
int newroot = tot++;
int tmp = newroot;
c[newroot] = c[root] + val;
int l = 0,r = num - 1;
while(l < r)
{
int mid = (l + r) >> 1;
if(mid >= pos)
{
lson[newroot] = tot++;
rson[newroot] = rson[root];
newroot = lson[newroot];
root = lson[root];
r = mid;
}
else
{
lson[newroot] = lson[root];
rson[newroot] = tot++;
newroot = rson[newroot];
root = rson[root];
l = mid + 1;
}
c[newroot] = c[root] + val;
}
return tmp;
}
void add(int x,int pos,int val) //更新树状数组
{
while(x <= n)
{
S[x] = insert1(S[x],pos,val);
x += x&(-x);
}
}
int sum(int x)
{
int ret = 0;
while(x > 0)
{
ret += c[lson[use[x]]];
x -= (x & (-x));
}
return ret;
}//计算前缀和
int qurry(int ll,int rr,int pos)
{
int i;
int rootl = T[ll];
int rootr = T[rr];
for(i = ll; i > 0 ; i-= (i & (-i))) use[i] = S[i];
for(i = rr; i > 0 ; i -= (i&(-i))) use[i] = S[i];
int l = 0,r = num - 1;
while(l < r)
{
int mid = (l + r) >> 1;
int cou = sum(rr) -sum(ll) + c[lson[rootr]] - c[lson[rootl]];
// printf("cou = %d\n",cou);
if(cou < pos)
{
pos -= cou;
for(i = ll; i > 0; i -= (i & (-i))) use[i] = rson[use[i]];
for(i = rr; i > 0; i -= (i & (-i))) use[i] = rson[use[i]];
rootl = rson[rootl];
rootr = rson[rootr];
l = mid + 1;
}
else
{
for(i = ll; i > 0; i -= i & (-i)) use[i] = lson[use[i]];
for(i = rr; i > 0; i -= i & (-i)) use[i] = lson[use[i]];
rootl = lson[rootl];
rootr = lson[rootr];
r = mid;
}
}
return l;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&n,&m);
int i,j;
num = tot = 0;
for(i = 0; i < n; i++)
{
scanf("%d",&a[i]);
b[num++] = a[i];
}
char str[10];
for(i = 0; i < m; i++)
{
scanf("%s %d %d",str,&q[i].l,&q[i].r);
if(str[0] == 'Q')
{
q[i].kind = 0;
scanf("%d",&q[i].w);
}
else
{
q[i].kind = 1;
b[num++] = q[i].r;
}
}
hase(num);
T[0] = build(0,num-1);
for(i = 1; i <= n; i++)
{
T[i] = insert1(T[i-1],get_hase(a[i-1]),1);
}
for(i = 1; i <= n; i++)
S[i] = T[0];
for(i = 0; i < m; i++)
{
if(q[i].kind == 0)
{
printf("%d\n",b[qurry(q[i].l - 1,q[i].r,q[i].w)]);
}
else
{
int v = get_hase(q[i].r);
add(q[i].l,get_hase(a[q[i].l-1]),-1);
add(q[i].l,v,1);
a[q[i].l - 1] = q[i].r;
}
}
}
return 0;
}