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