ZOJ-2112-Dynamic Rankings(主席树动态第k大)

本文介绍了一种结合主席树和树状数组的数据结构解决方案,用于高效处理一系列数值的查询与更新操作,特别是在需要频繁查询区间内第k小元素及更新元素场景下。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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个数m次操作,Q  查询l到r中第k大的数,C  将下标为l的数改成r;

思路:很明显的主席树,但是加了修改操作。首先思考如果就在原来的主席树上修改,每次修改下标为i的点,那么从i-n棵主席树都要修改,这样时间复杂度肯定炸了。既然主树维护的前缀和,那么么不妨用另一种数据结构来保存修改的信息,维护前缀和的可以想到用树状数组,每次修改只用修改logn各点,树状数组的每个结点也表示一个主席树,对于原数组建好静态主席树之后就不变了,修改操作都在树状数组上进行;详细看代码把,自认为比较容易懂;

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=6e4+10;
struct node{int l,r,cnt;}tree[maxn*40];
struct qy{int tp,l,r,k;}Q[maxn];
int n,m,arr[maxn],root[maxn],cnt,sz;
int s[maxn],vis[maxn];
vector<int> v;
int getid(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}
int update(int l,int r,int &x,int y,int pos,int tp,int id)///id为树状数组s[i]对应的主席树根的信息,其他的都是常规操作
{
    x=++cnt;
    tree[x]=tree[y];
    tree[x].cnt+=tp;
    if(!id) id=cnt;
    if(l==r) return id;
    int mid=(l+r)>>1;
    if(pos<=mid) return update(l,mid,tree[x].l,tree[y].l,pos,tp,id);
    else return update(mid+1,r,tree[x].r,tree[y].r,pos,tp,id);
}
int getsum(int x)
{
    int sum=0;
    while(x){
        sum+=tree[tree[vis[x]].l].cnt;
        x-=x&(-x);
    }
    return sum;
}
void add(int x,int pos,int tp)
{
    while(x<=sz){
        s[x]=update(1,sz,s[x],s[x],pos,tp,0);
        x+=x&(-x);
    }
}

int query(int r,int l,int k)
{
    int left=1,right=sz,ll=root[l],rr=root[r];
    for(int i=l;i;i-=i&(-i)) vis[i]=s[i];///vis[i]为要修改的树状数组上的点
    for(int i=r;i;i-=i&(-i)) vis[i]=s[i];
    while(left<right){
        int tmp=tree[tree[rr].l].cnt-tree[tree[ll].l].cnt+getsum(r)-getsum(l);///用原主席树的信息加上修改后的信息
        int mid=(left+right)>>1;
        if(tmp>=k){
            for(int i=l;i;i-=i&(-i)) vis[i]=tree[vis[i]].l;
            for(int i=r;i;i-=i&(-i)) vis[i]=tree[vis[i]].l;
            right=mid;
            rr=tree[rr].l;
            ll=tree[ll].l;
        }
        else{
            for(int i=l;i;i-=i&(-i)) vis[i]=tree[vis[i]].r;
            for(int i=r;i;i-=i&(-i)) vis[i]=tree[vis[i]].r;
            k-=tmp;
            left=mid+1;
            rr=tree[rr].r;
            ll=tree[ll].r;
        }
    }
    return v[left-1];
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        v.clear();
        for(int i=1;i<=n;i++) scanf("%d",&arr[i]),v.push_back(arr[i]);
        for(int i=1;i<=m;i++){
            char op[3];scanf("%s",op);
            int l,r,k;
            if(op[0]=='Q'){
                Q[i].tp=1;
                scanf("%d%d%d",&Q[i].l,&Q[i].r,&Q[i].k);
            }
            else{
                Q[i].tp=0;
                scanf("%d%d",&Q[i].l,&Q[i].r);
                v.push_back(Q[i].r);///修改的数字也要加入vector中
            }
        }
        sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());///排序去重,效率比较高。
        cnt=0;
        sz=v.size();///
        for(int i=1;i<=n;i++){
            s[i]=0;
            update(1,sz,root[i],root[i-1],getid(arr[i]),1,0);///对于原数组建好主席树
        }
        for(int i=1;i<=m;i++){
            if(Q[i].tp==1){
                printf("%d\n",query(Q[i].r,Q[i].l-1,Q[i].k));
            }
            else{
                add(Q[i].l,getid(arr[Q[i].l]),-1);///先去掉原先的数字的影响
                add(Q[i].l,getid(Q[i].r),1);///加上修改的数字残生的影响
                arr[Q[i].l]=Q[i].r;
            }
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值