题目描述
您需要写一种数据结构,来维护一个有序数列,其中需要提供以下操作:
-
查询k在区间内的排名
-
查询区间内排名为k的值
-
修改某一位值上的数值
-
查询k在区间内的前驱(前驱定义为严格小于x,且最大的数,若不存在输出-2147483647)
-
查询k在区间内的后继(后继定义为严格大于x,且最小的数,若不存在输出2147483647)
输入格式:
第一行两个数 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的后继
输出格式:
对于操作1,2,4,5各输出一行,表示查询结果
输入样例#1:
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
输出样例#1:
2 4 3 4 9
[树套树]
我们熟知,平衡树可以查询整个数列的第K大,但是不能查询部分序列
线段树可以提取某个区间
于是线段树+平衡树即树套树可以支持部分区间的第K大,前驱,后继...
如何实现呢
树套树是一种很暴力的数据结构
可能没有懂...
通俗解释一下----
1--4 区间对应的Treap里有4个值, 1--2 区间对应的Treap有2个值....
我们每插入一个节点都要把到线段树最底下的路径上的点对应的Treap都插入一个它
比如说我们要在3这个位置插入一个4
从线段树的根到3要经过的区间有 1--4, 3--4,3--3
于是这3个区间对应的Treap都会插入一个4这个值
这样做有什么好处呢?? 如果我们要查询2--4的信息,我们递归找到2--2,3--4
到3--4就不用再找了,可以直接从3--4区间的Treap中查询答案
root[i]有什么用呢??
3--4区间对应线段树上的值是3,而3--4区间的Treap的根可能是9
我们记录rt[3]=9 , 如果要查询3--4 这个区间,我们只需要直接在Treap中查(rt[3],...)
代码
#include<bits/stdc++.h>
#define N 100005
#define inf 2147483647
#define lc tr[x].ch[0]
#define rc tr[x].ch[1]
using namespace std;
int a[N],rt[N<<2],ans;
struct Node{int l,r,val;}t[N<<2];
struct Treap{int ch[2],val,p,size,num;}tr[N*20];
int n,m,tot;
int read(){
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch))cnt=cnt*10+(ch-'0'),ch=getchar();
return cnt*f;
}
int randon(){
static int seed=233;
return seed=int(seed*47281LL%0x3fffffff);
}
void Pushup(int x){
tr[x].size=tr[lc].size+tr[rc].size+tr[x].num;
}
void rotate(int &x,int k){
int y=tr[x].ch[k];
tr[x].ch[k]=tr[y].ch[k^1];
tr[y].ch[k^1]=x;
tr[y].size=tr[x].size;
Pushup(x); x=y;
}
void insert(int &x,int val){
if(!x){x=++tot;tr[x].val=val,tr[x].size=tr[x].num=1,tr[x].p=randon();return;}
tr[x].size++;
if(tr[x].val==val){tr[x].num++;return;}
if(val<tr[x].val){
insert(lc,val);
if(tr[lc].p<tr[x].p) rotate(x,0);
}
else{
insert(rc,val);
if(tr[rc].p<tr[x].p) rotate(x,1);
}
}
void erase(int &x,int val){
if(!x) return;
if(tr[x].val==val){
if(tr[x].num>1) tr[x].num--,tr[x].size--;
else if(lc*rc==0) x=lc+rc;
else{
if(tr[lc].p<tr[rc].p) rotate(x,0);
else rotate(x,1);
erase(x,val);
}
}
else{
tr[x].size--;
if(val<tr[x].val) erase(lc,val);
else erase(rc,val);
}
}
void rank(int x,int k){
if(!x) return;
if(tr[x].val==k) ans+=tr[lc].size;
else if(k<tr[x].val) rank(lc,k);
else ans+=tr[lc].size+tr[x].num,rank(rc,k);
}
void Build(int x,int l,int r,int pos,int val){
t[x].l=l,t[x].r=r;
insert(rt[x],val);
if(l==r) return;
int mid=(l+r)>>1;
if(pos<=mid) Build(x<<1,l,mid,pos,val);
else Build(x<<1|1,mid+1,r,pos,val);
}
void Rank(int x,int L,int R,int k){
if(L<=t[x].l&&t[x].r<=R){rank(rt[x],k);return;}
int mid=(t[x].l+t[x].r)>>1;
if(L<=mid) Rank(x<<1,L,R,k);
if(R>mid) Rank(x<<1|1,L,R,k);
}
void Kth(int L,int R,int k){
int l=1,r=inf;
while(l<r){
int mid=(l+r+1)>>1;
ans=1,Rank(1,L,R,mid);
if(ans<=k) l=mid;
else r=mid-1;
}
printf("%d\n",l);
}
void Change(int x,int pos,int last,int val){
erase(rt[x],last);
insert(rt[x],val);
if(t[x].l==t[x].r) return;
int mid=(t[x].l+t[x].r)>>1;
if(pos<=mid) Change(x<<1,pos,last,val);
else Change(x<<1|1,pos,last,val);
}
void Pre(int x,int val){
if(!x) return;
if(tr[x].val<val){
ans=max(ans,tr[x].val),Pre(rc,val);
}
else Pre(lc,val);
}
void Suf(int x,int val){
if(!x) return;
if(val<tr[x].val){
ans=min(ans,tr[x].val),Suf(lc,val);
}
else Suf(rc,val);
}
void Ask(int op,int x,int L,int R,int k){
if(L<=t[x].l&&t[x].r<=R){
if(op==1) Pre(rt[x],k);
else Suf(rt[x],k);
return;
}
int mid=(t[x].l+t[x].r)>>1;
if(L<=mid) Ask(op,x<<1,L,R,k);
if(R>mid) Ask(op,x<<1|1,L,R,k);
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read(),Build(1,1,n,i,a[i]);
}
while(m--){
int op=read(),x=read(),y=read();
if(op==1){
int k=read();
ans=1,Rank(1,x,y,k);
printf("%d\n",ans);
}
if(op==2){
int k=read();
Kth(x,y,k);
}
if(op==3) Change(1,x,a[x],y),a[x]=y;
if(op==4){
int k=read();
ans=-inf,Ask(1,1,x,y,k);
printf("%d\n",ans);
}
if(op==5){
int k=read();
ans=inf,Ask(2,1,x,y,k);
printf("%d\n",ans);
}
}
}