Description
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
1.查询k在区间内的排名
2.查询区间内排名为k的值
3.修改某一位值上的数值
4.查询k在区间内的前驱(前驱定义为小于x,且最大的数)
5.查询k在区间内的后继(后继定义为大于x,且最小的数)
Input
第一行两个数 n,m 表示长度为n的有序序列和m个操作
第二行有n个数,表示有序序列
下面有m行,opt表示操作标号
若opt=1 则为操作1,之后有三个数l,r,k 表示查询k在区间[l,r]的排名
若opt=2 则为操作2,之后有三个数l,r,k 表示查询区间[l,r]内排名为k的数
若opt=3 则为操作3,之后有两个数pos,k 表示将pos位置的数修改为k
若opt=4 则为操作4,之后有三个数l,r,k 表示查询区间[l,r]内k的前驱
若opt=5 则为操作5,之后有三个数l,r,k 表示查询区间[l,r]内k的后继
Output
对于操作1,2,4,5各输出一行,表示查询结果
Sample Input
9 6
4 2 2 1 9 4 0 1 1
2 1 4 3
3 4 10
2 1 4 3
1 2 5 9
4 3 9 5
5 2 8 5
Sample Output
2
4
3
4
9
HINT
1.n和m的数据范围:n,m<=50000
2.序列中每个数的数据范围:[0,1e8]
3.虽然原题没有,但事实上5操作的k可能为负数
由于这题既要维护区间的信息,又要搞排名,前驱,后继等平衡树的操作。所以这题的思路就是线段树套平衡树(树套树)
我们对于线段树的每一个区间都建立一个平衡树,那么我们就可以进行操作了。我们一个一个来看:
操作1:我们查询线段树的区间,将每一小块区间里小于x的数累加,再加1(x自己)即可。
操作3:为什么不先操作2?因为操作2是所有操作里最繁琐的,就在线段树里一直递归到这个点,然后在途经的所有平衡树中删去这个点的权值,再加进x的值即可。
操作4:在线段树里每一个小块找x的前驱,最后取个max。
操作5:在线段树里每一个小块找x的后继,最后取min。
现在我们来讲操作2:直接二分答案,二分一个权值,然后查询区间里有的排名,如果刚好等于k,那么就让r=mid,这样可以保证最终的点取到所求的x的权值vx(因为vx-1的排名肯定是x的排名-1)
int GetPl(int l,int r,int k){
int cl=0,cr=MX+1,mid;
while(cl<cr){
mid=(cl+cr)>>1;
dist=0;SegRank(1,1,n,l,r,mid);
if(dist<k) cl=mid+1;
else cr=mid;
}
return cl-1;
}
Code:
#include<bits/stdc++.h>
#define MAXN 3000005
#define INF 2147483647
using namespace std;
int read(){
char c;int x=0,y=1;while(c=getchar(),(c<'0'||c>'9')&&c!='-');
if(c=='-') y=-1;else x=c-'0';while(c=getchar(),c>='0'&&c<='9')
x=x*10+c-'0';return x*y;
}
int n,m,MX,dist,tot,a[MAXN],rt[MAXN];
int son[MAXN][2],fa[MAXN],val[MAXN],siz[MAXN],ct[MAXN];
void clear(int k){
fa[k]=son[k][1]=son[k][0]=val[k]=siz[k]=ct[k]=0;
}
void pushup(int k){ //统计子树大小
siz[k]=(son[k][0]?siz[son[k][0]]:0)+(son[k][1]?siz[son[k][1]]:0)+ct[k];
}
void rotate(int k){
int f=fa[k],gran=fa[f],d=son[f][1]==k,w=son[k][d^1];
son[f][d]=w;fa[f]=k;son[k][d^1]=f;if(w) fa[w]=f;
if(gran) son[gran][son[gran][1]==f]=k;fa[k]=gran;
pushup(f);pushup(k);
}
void splay(int i,int k,int top){ //splay操作
while(fa[k]!=top){
int f=fa[k],gran=fa[f];
if(gran!=top) rotate((son[f][1]==k)^(son[gran][1]==f)?k:f);
rotate(k);
}
if(!top) rt[i]=k;
}
void SplayInsert(int p,int x){ //在splay里添加点
int wh=rt[p];
if(!rt[p]){
rt[p]=wh=++tot;fa[wh]=0;
val[wh]=x;siz[wh]=ct[wh]=1;
son[wh][0]=son[wh][1]=0;
return;
}
int last=0;
while(1){
if(val[wh]==x){ct[wh]++;pushup(wh);break;}
last=wh;wh=son[wh][x>val[wh]];
if(!wh){
wh=++tot;val[wh]=x;son[last][x>val[last]]=wh;
son[wh][0]=son[wh][1]=0;
fa[wh]=last;siz[wh]=ct[wh]=1;
pushup(last);break;
}
}
splay(p,wh,0);
}
int SplayRank(int p,int k){ //查询在splay里的排名
int wh=rt[p],ret=0;
while(wh){
if(val[wh]>k) wh=son[wh][0];
else{
if(val[wh]<k){
ret+=(son[wh][0]?siz[son[wh][0]]:0)+ct[wh];wh=son[wh][1];
}
else if(val[wh]==k) return ret+(son[wh][0]?siz[son[wh][0]]:0);
}
}
return ret;
}
int access(int p,int x){ //将权值为x的点变为splay的根
int wh=rt[p];
while(wh){
if(val[wh]==x){
splay(p,wh,0);return wh;
}
wh=son[wh][x>val[wh]];
}
}
int pre(int p){int x=son[rt[p]][0];while(son[x][1])x=son[x][1];return x;}
void SplayDelete(int p,int elemental){ //splay删点
int x=access(p,elemental);
if(ct[x]>1){ct[x]--;pushup(x);return;}
if(!son[x][0]&&!son[x][1]){clear(x);rt[p]=0;return;}
if(!son[x][0]){
int y=son[x][1];rt[p]=y;fa[y]=0;
return;
}
if(!son[x][1]){
int y=son[x][0];rt[p]=y;fa[y]=0;
return;
}
int wh=pre(p),oldrt=rt[p];
splay(p,wh,0);
son[rt[p]][1]=son[oldrt][1];fa[son[oldrt][1]]=rt[p];
clear(oldrt);pushup(rt[p]);
}
int GetPre(int p,int x){ //splay里找前驱
int wh=rt[p];
while(wh){
if(val[wh]<x){
dist=max(dist,val[wh]);
wh=son[wh][1];
}
else wh=son[wh][0];
}
return dist;
}
int GetSuff(int p,int x){ //splay里找后继
int wh=rt[p];
while(wh){
if(val[wh]>x){
dist=min(dist,val[wh]);
wh=son[wh][0];
}
else wh=son[wh][1];
}
return dist;
}
void SegInsert(int k,int l,int r,int p,int x){ //线段树差点
SplayInsert(k,x);
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) SegInsert(k<<1,l,mid,p,x);
else SegInsert(k<<1|1,mid+1,r,p,x);
}
void SegRank(int k,int l,int r,int L,int R,int x){ //查询区间排名
if(L<=l&&r<=R){
dist+=SplayRank(k,x);return;
}
int mid=(l+r)>>1;
if(L<=mid) SegRank(k<<1,l,mid,L,R,x);
if(R>mid) SegRank(k<<1|1,mid+1,r,L,R,x);
}
void SegTransform(int k,int l,int r,int p,int x){ //改某个点的权值
SplayDelete(k,a[p]);SplayInsert(k,x);
if(l==r){a[p]=x;return;}
int mid=(l+r)>>1;
if(p<=mid) SegTransform(k<<1,l,mid,p,x);
else SegTransform(k<<1|1,mid+1,r,p,x);
}
void SegPre(int k,int l,int r,int L,int R,int x){ //查询区间前驱
if(L<=l&&r<=R){
dist=max(dist,GetPre(k,x));
return;
}
int mid=(l+r)>>1;
if(L<=mid) SegPre(k<<1,l,mid,L,R,x);
if(R>mid) SegPre(k<<1|1,mid+1,r,L,R,x);
}
void SegSuff(int k,int l,int r,int L,int R,int x){ //查询区间后继
if(L<=l&&r<=R){
dist=min(dist,GetSuff(k,x));
return;
}
int mid=(l+r)>>1;
if(L<=mid) SegSuff(k<<1,l,mid,L,R,x);
if(R>mid) SegSuff(k<<1|1,mid+1,r,L,R,x);
}
int GetPl(int l,int r,int k){ //查询排名为x的点
int cl=0,cr=MX+1,mid;
while(cl<cr){
mid=(cl+cr)>>1;
dist=0;SegRank(1,1,n,l,r,mid);
if(dist<k) cl=mid+1;
else cr=mid;
}
return cl-1;
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++){
a[i]=read();
SegInsert(1,1,n,i,a[i]);
MX=max(MX,a[i]);
}
for(int i=1;i<=m;i++){
int type=read();
if(type==1){
int l=read(),r=read(),k=read();dist=0;
SegRank(1,1,n,l,r,k);printf("%d\n",dist+1);
}
if(type==2){
int l=read(),r=read(),k=read();
printf("%d\n",GetPl(l,r,k));
}
if(type==3){
int p=read(),x=read();
SegTransform(1,1,n,p,x);
}
if(type==4){
int l=read(),r=read(),k=read();dist=-INF;
SegPre(1,1,n,l,r,k);printf("%d\n",dist);
}
if(type==5){
int l=read(),r=read(),k=read();dist=INF;
SegSuff(1,1,n,l,r,k);printf("%d\n",dist);
}
}
return 0;
}
本文介绍了一种数据结构——线段树套平衡树,用于维护有序数列并支持多种操作,包括查询区间排名、查询排名对应的值、修改数值、查询前驱与后继等。文章提供了详细的实现代码及样例输入输出。
739

被折叠的 条评论
为什么被折叠?



