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


(adviser)
Site: http://zhuzeyuan.hp.infoseek.co.jp/index.files/our_contest_20040619.htm

题目大意:就是有修改的询问区间第k小。

解题思路:没有修改操作的话,有很多种解题方法,划分树,树状数组离线(固定右端点),主席树。

同样,对于带有修改操做,一样能用主席树来解决。

1.按照原来的方法,将询问的点同原数组一起离散化放进主席树里。

2.因为一颗树记录的是数组前缀各个区间的数字个数,我们要更新第i个数字的话我们就要把第i棵树以及以后的树全扫一边然后更新,这样的话会超时。

3.对于修改一个数字(假设把第i个数字i变成了j)我们考虑他的影响,就是当前位置到N所有包括 i 的区间的sum都将减少1,所有包含

j的区间都将加上1.

4.这个类似于区间修改(把i到n包含这个数的区间都减去1,然后把包含更新后的数字的区间都加上1),单点查询,因此我们可以借助树状数组来配合主席树完成。

5.这样修改的话对于原来的查需我们还在原来的静态主席树上进行。

6.当修改时,我们树状数组每一个节点都是一棵树的根节点的值,此时如果我们再更新第i棵树到第n棵树时,我们只需要更新logn

棵树就行了(每棵树更新logn个节点)。

7.最后在查询时,我们要加上树状数组记录的值(对答案的影响)就行了。

注意:这里记录一下需要注意的点,原名我想对于每一个节点建立一颗树的话,这样的话就不用每次update的时候动态开点了(具体见下方代码),后来想了一下我们之所以动态开点不就是为了解决这个问题吗?初始时每个树状数组的节点对应T[0],也就是一颗空树。此外为了查需下推时不出错,我们用两个数组ul,ur记录当前查询树状数组上节点的左右子节点。当查询到下一层时ul和ur也要向下推,就是Go函数中的内容。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define lowb(x) x&-x
#define N 60005

int n,cnt;
struct node
{
    int f,l,r,k;
}q[N];
int T[N],L[N*32],R[N*32],sum[N*32];//空间大概是原来的空间加上m*logn*logn
int a[N],b[N],S[N];
int tot;

void build(int &rt,int l,int r)
{
    rt=++tot;
    sum[rt]=0;
    if(l==r)return ;
    int m=(l+r)>>1;
    build(L[rt],l,m);
    build(R[rt],m+1,r);
}

void update(int &rt,int pre,int l,int r,int k,int val)
{
    rt=++tot;
    L[rt]=L[pre],R[rt]=R[pre];
    sum[rt]=sum[pre]+val;
    if(l==r)return ;
    int m=(l+r)>>1;
    if(k<=m) update(L[rt],L[pre],l,m,k,val);
    else update(R[rt],R[pre],m+1,r,k,val);
}//建树和更新都与静态主席树一样。

void add(int x,int val)
{
    int p=lower_bound(b+1,b+1+cnt,a[x])-b;
    while(x<=n)
    {
        update(S[x],S[x],1,cnt,p,val);
        x+=lowb(x);
    }
}
//修改第i棵树到第n棵树(区间修改)
int ul[N],ur[N];

void Go(int l,int r,int f)
{
    if(!f){
    for(int i=l;i;i-=lowb(i))ul[i]=L[ul[i]];
    for(int i=r;i;i-=lowb(i))ur[i]=L[ur[i]];
    }
    if(f){
    for(int i=l;i;i-=lowb(i))ul[i]=R[ul[i]];
    for(int i=r;i;i-=lowb(i))ur[i]=R[ur[i]];
    }
}//因为时动态开点,所以我们要记录当前节点左右子节点的位置。

int SUM(int p,int f)
{
    int ans=0;
    while(p)
    {
        if(f)ans+=sum[L[ur[p]]];
        else ans+=sum[L[ul[p]]];
        p-=lowb(p);
    }
    return ans;
}

int query(int s,int e,int l,int r,int ql,int qr,int k)
{
    if(l==r)return l;
    int now=SUM(qr,1)-SUM(ql,0)+sum[L[e]]-sum[L[s]];
    int m=(l+r)>>1;
    if(k<=now)
    {
        Go(ql,qr,0);
        return query(L[s],L[e],l,m,ql,qr,k);
    }
    else
    {
        Go(ql,qr,1);
        return query(R[s],R[e],m+1,r,ql,qr,k-now);
    }
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            b[i]=a[i];
        }
        char s[8];
        cnt=n;

        for(int i=1;i<=m;i++)
        {
            scanf("%s",s);
            if(s[0]=='Q')
            {
                q[i].f=1;
                scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].k);
            }
            else
            {
                q[i].f=0;
                scanf("%d%d",&q[i].l,&q[i].k);
                b[++cnt]=q[i].k;
            }
        }

        sort(b+1,b+1+cnt);
        int tmp = unique(b+1,b+1+cnt)-b-1;
        cnt=tmp;
        tot=0;//重要 tot的初始化
        build(T[0],1,cnt);
        for(int i=1;i<=n;i++)update(T[i],T[i-1],1,cnt,lower_bound(b+1,b+1+cnt,a[i])-b,1);
        for(int i=1;i<=n;i++)S[i]=T[0];//初始时每个都对应一颗空树的根节点。
        for(int i=1;i<=m;i++)
        {
            if(q[i].f)
            {
                int ql=q[i].l;
                int qr=q[i].r;
                for(int j=ql-1;j;j-=lowb(j)) ul[j]=S[j];//为树状数组上的节点下推做准备。
                for(int j=qr;j;j-=lowb(j)) ur[j]=S[j];
                printf("%d\n",b[query(T[ql-1],T[qr],1,cnt,ql-1,qr,q[i].k)]);
            }
            else
            {
                add(q[i].l,-1);
                a[q[i].l]=q[i].k;//这里要先更新后add
                add(q[i].l,1);
                //cout<<"del late="<<sum[S[q[i].l]]<<endl;
            }
        }
    }
}

参考博客:https://www.cnblogs.com/Empress/p/4659824.html

猜你喜欢

转载自blog.csdn.net/weixin_40894017/article/details/82632508