zoj-2112-Dynamic Rankings主席树模板

Dynamic Rankings

Time Limit: 10 Seconds      Memory Limit: 32768 KB

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;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值