ZOJ 2112 Dynamic Rankings(带修改的主席树)

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

题意:

n个数,q个询问 (n<=50000, q<=10000)

Q x y z 代表询问[x, y]区间里的第z小的数

C x y    代表将(从左往右数)第x个数变成y

 

解析:

详细解析

代码参考

我看了大神讲半天看不懂,就直接啃代码,边看代码,边看题解

总的简单总结一下就是

树状数组维护的主席树(线段树)
就优化了前缀和的性质
最普通的主席树,i的前缀和树是用root[i]来存的
但是用树状数组,就是用
for(;i>0;i-=lowbit(i)) use[i]=s[j];
这几棵树共同存储i的前缀和树
这样虽然看上去有点多此一举,但是可以用于带修改的主席树。
因为修改一个点j,按照普通的主席树,你需要修改root[j,n]中
的树,这样复杂度就会变大,用树状数组优化你就只需要更新
for(i=j;i<=n;i+=lowbit(i)) 这几颗树就可以了。

use就像一个卡带一样,只有当前需要计算的树它才会留有孔,
不需要的树就没有孔,又因为这些树的结构都是一样的,
所以相当于在一颗树上移动一样,移动到左孩子,对应卡带就会移动

ZOJ 2112 Dynamic Rankings(带修改的主席树)

对于这里的更新主席树中的一棵树root[x],我们肯定要把x的历史版本
past=root[x]给放弃掉,而不是像初始的建树的时候还进行迭代保存
(即root[x]=past,root[x]=newroot)。因为这里root[x]就是以序列下标为
key,而不是版本的次序,并且对于历史版本对于这道题根本没有任何用处
。所以就直接丢弃。

#include <bits/stdc++.h>
#define lowbit(x) ( x&( -x ) )
using namespace std;
typedef  long long ll;

const int MAXN = 5e4+110;
const int M = 1e4+100;
int n,m;
ll a[MAXN];
ll ran[MAXN*2];

struct node
{
    char id;
    int l,r,k;
}Q[M];

int rt[MAXN],ls[MAXN*50],rs[MAXN*50],tot,sz;
int sum[MAXN*50];
int use[MAXN],S[MAXN];
int RIGHT,LEFT;


int getSum(int pos)  //区间查询
{
    int ans=0;
    while(pos)
    {
        ans+=sum[ls[use[pos]]];  //找第k小的数首先判断左区间
        pos-=lowbit(pos);
    }
    return ans;
}

void build(int& root,int l,int r)
{
    root=++tot;
    sum[root]=0;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(ls[root],l,mid);
    build(rs[root],mid+1,r);
}

void update(int& root,int l,int r,int last,int x,int val)
{
    root=++tot;
    ls[root]=ls[last];
    rs[root]=rs[last];
    sum[root]=sum[last]+val;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(mid>=x) update(ls[root],l,mid,ls[last],x,val);
    else update(rs[root],mid+1,r,rs[last],x,val);
}


int query(int ss,int tt,int l,int r,int k) //第k小
{

    if(l==r) return l;
    int rest=getSum(RIGHT)-getSum(LEFT);
    int cnt=sum[ls[tt]]-sum[ls[ss]];
    cnt+=rest;
    int mid=(l+r)>>1;
    if(k<=cnt)
    {
        for(int j=RIGHT;j>0;j-=lowbit(j)) use[j]=ls[use[j]];  //进入1..RIGHT的前缀和树当前区间的左区间
        for(int j=LEFT;j>0;j-=lowbit(j)) use[j]=ls[use[j]];   //进入1..LEFT的前缀和树当前区间的左区间
        return query(ls[ss],ls[tt],l,mid,k);
    }
    else
    {
        for(int j=RIGHT;j>0;j-=lowbit(j)) use[j]=rs[use[j]];  //进入1..RIGHT的前缀和树当前区间的右区间
        for(int j=LEFT;j>0;j-=lowbit(j)) use[j]=rs[use[j]];   //进入1..LEFT的前缀和树当前区间的右区间
        return query(rs[ss],rs[tt],mid+1,r,k-cnt);
    }
}

void input()
{
    sz=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        ran[++sz]=a[i];
    }
    for(int i=1;i<=m;i++){
        getchar();
        scanf("%c",&Q[i].id);
        if(Q[i].id=='Q'){
            scanf("%d%d%d",&Q[i].l,&Q[i].r,&Q[i].k);

        } else{
            scanf("%d%d",&Q[i].l,&Q[i].k);
            ran[++sz]=Q[i].k;
        }
    }

    tot=0;
    sort(ran+1,ran+1+sz);
    sz=unique(ran+1,ran+1+sz)-(ran+1);
    build(rt[0],1,sz);
    for(int i=1;i<=n;i++)
    {
        int pos=lower_bound(ran+1,ran+1+sz,a[i])-ran;
        update(rt[i],1,sz,rt[i-1],pos,1);
    }

    for(int i=1;i<=n;i++)  //一开始全为空树
        S[i]=rt[0];

}

void Change(int x,int pos,int val) //单点更新
{
    int past;
    while(x<=n)
    {
        past=S[x];
        update(S[x],1,sz,past,pos,val);
        x+=lowbit(x);
    }
}

void solve()
{
    for(int i=1;i<=m;i++)
    {
        if(Q[i].id=='Q'){
            RIGHT=Q[i].r;
            LEFT=Q[i].l-1;
            for(int j=RIGHT;j>0;j-=lowbit(j)) use[j]=S[j];  //记录1..RIGHT的前缀和树
            for(int j=LEFT;j>0;j-=lowbit(j)) use[j]=S[j];   //记录1..LEFT的前缀和树
            //use用于遍历树状数组表示的每一棵树
            //user[j]表示第j棵树已经遍历到它的编号为use[j]的点


            int ind=query(rt[Q[i].l-1],rt[Q[i].r],1,sz,Q[i].k);
            printf("%lld\n",ran[ind]);

        } else{

            int pos=lower_bound(ran+1,ran+1+sz,a[Q[i].l])-ran;
            Change(Q[i].l,pos,-1);
            pos=lower_bound(ran+1,ran+1+sz,Q[i].k)-ran;
            Change(Q[i].l,pos,1);
            a[Q[i].l]=Q[i].k;

        }

    }
}



int main()
{
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
    int t;
    scanf("%d",&t);
    while(t--)
    {
        input();
        solve();
    }
}