BZOJ 1901 Zju2112 Dynamic Rankings

本文介绍了一种解决动态排名问题的方法,通过使用主席树和树状数组来高效处理序列中数值的查询与修改操作。文章详细解释了如何构建和维护这些数据结构,并给出了一段完整的实现代码。

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

title: ‘BZOJ 1901 Zju2112 Dynamic Rankings’
categories: BZOJ
date: 2016-1-6 13:23:00
tags: [主席树,树状数组]


Description

给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。对于每一个询问指令,你必须输出正确的回答。

Input

第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。分别表示序列的长度和指令的个数。第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。接下来的m行描述每条指令,每行的格式是下面两种格式中的一种。 Q i j k 或者 C i t Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t。

Output

对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。

Sample

input.txt
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3

output.txt
3
6

Hint

20%的数据中,m,n≤100; 40%的数据中,m,n≤1000; 100%的数据中,m,n≤10000。

Solution

第一次打主席树就是这种题。。。
带修改的区间第k大,具体做法可以看一波clj论文:论文戳这里
首先用线段树求全局第k大,感觉跟Splay差不多,根据左右儿子的size值来考虑进入左孩子还是右孩子。
然后根据权值线段树的运算法则,若用T(l,r)表示区间[l,r]的权值线段树,可以以用T(1,r)-T(1,l-1)表示之,这就相当于是用线段树表示了前缀和。因此我们只需要建T(1,1),T(1,2),….,T(1,n)这n棵线段树即可,用可持久化线段树得到所有的T只需要O(nlogn)的时间和空间。
然而若需要修改,则要修改n个前缀和,单次修改和询问复杂度会上升到O(nlogn)。
有没有什么方便维护的前缀和?对了,树状数组!于是我们可以开O(logn)个权值线段树,表示与树状数组类似的意义来维护这个前缀和,因此每次询问和修改的复杂度只需要O((logn)^2)。
哦对了= = 注意离散化,23333

Code

#include<map>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

#define maxn 20000+5
#define maxm 2400000+5

using namespace std;

struct Ha_Tree{
    int l,r;
    int sum;
}tr[maxm];

struct Query_kth{
    char s[1];
    int x,y,z;
}q[maxn];

int ori[maxn],a[maxn],c[maxn],root[maxn],L[maxn],R[maxn];
int n,m,cnt,tot,ind,k,pl,pr;

int find(int val){
    int l=1,r=tot;
    while(l<=r){
        int mid=(l+r)>>1;
        if(c[mid]>val) r=mid-1;
        else if(c[mid]<val) l=mid+1;
        else return mid;
    }
}

void Update(int l,int r,int last,int &k,int pos,int val){
    k=++ind;
    tr[k]=tr[last]; tr[k].sum+=val;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(pos<=mid) Update(l,mid,tr[last].l,tr[k].l,pos,val);
    else Update(mid+1,r,tr[last].r,tr[k].r,pos,val);
}

void Modify(int x,int pos,int val){
    for(int i=x;i<=n;i+=i&-i)
        Update(1,tot,root[i],root[i],pos,val);
}

int Query(int l,int r,int k){
    if(l==r) return l;
    int mid=(l+r)>>1;
    int suml=0,sumr=0;
    for(int i=1;i<=pl;i++) suml+=tr[tr[L[i]].l].sum;
    for(int i=1;i<=pr;i++) sumr+=tr[tr[R[i]].l].sum;
    if(sumr-suml>=k){
        for(int i=1;i<=pl;i++) L[i]=tr[L[i]].l;
        for(int i=1;i<=pr;i++) R[i]=tr[R[i]].l;
        return Query(l,mid,k);
    }
    else{
        for(int i=1;i<=pl;i++) L[i]=tr[L[i]].r;
        for(int i=1;i<=pr;i++) R[i]=tr[R[i]].r;
        return Query(mid+1,r,k-(sumr-suml));
    }
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&ori[i]),a[++cnt]=ori[i];
    for(int i=1;i<=m;i++){
        scanf("%s",q[i].s);
        scanf("%d%d",&q[i].x,&q[i].y);
        if(q[i].s[0]=='C') a[++cnt]=q[i].y;
        if(q[i].s[0]=='Q') scanf("%d",&q[i].z);
    }
    a[0]=-0x3f3f3f3f;
    sort(a+1,a+1+cnt);
    for(int i=1;i<=cnt;i++)
        if(a[i]!=a[i-1]) c[++tot]=a[i];   
    for(int i=1;i<=n;i++){
        int pos=find(ori[i]);
        Modify(i,pos,1);
    }
    for(int i=1;i<=m;i++)
        if(q[i].s[0]=='C'){
            int pos1=find(ori[q[i].x]),pos2=find(q[i].y);
            Modify(q[i].x,pos1,-1); Modify(q[i].x,pos2,1);
            ori[q[i].x]=q[i].y;     
        }
        else{
            int l=q[i].x-1,r=q[i].y; pl=pr=0;
            for(int j=l;j;j-=j&-j) L[++pl]=root[j];
            for(int j=r;j;j-=j&-j) R[++pr]=root[j];
            printf("%d\n",c[Query(1,tot,q[i].z)]);
        }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值