前置技能
应用及实现
我们要维护这样一个数列,它资瓷以下操作:
区间查询前驱、后继、数k的排名、排名为k的数,以及区间/单点修改。
乍一看很像平衡树,但又是区间操作,有点线段树的味道。那我们还不如干脆把他们“套”在一起,就可以资瓷上面的操作了。
但是要怎么套呢?
先根据原来的数列建一颗线段树,再给每个节点所代表的区间建一颗平衡树。于是我们就有了1+N颗树。当区间查询时,像线段树那样对应到每个区间,再在其对应的平衡树上查询即可。当修改时,把要修改的节点从包括这个位置的平衡树上删去,再把修改后的节点插入即可。
模板
以BZOJ3196(洛谷P3380)为例(结构体打到崩溃),这里是线段树套Treap:
#include<cctype>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define N 50001
#define inf 0x7fffffff
using namespace std;
struct tree{ int l,r,rt; }t[N<<2];//rt表示当前线段对应的平衡树的根节点编号
struct node{ int w,p,sz,rnd,to[2]; }nt[N<<6];
int n,m,nd,a[N];
inline char readc(){
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if (l==r) return EOF; return *l++;
}
inline int _read(){
int x=0,f=1; char ch=readc();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=readc(); }
while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc();
return x*f;
}
inline void _write(int x){
if(x<0) putchar('-'),x=-x;
if(x>9) _write(x/10); putchar(x%10+'0');
}
inline void write(int x){ _write(x),putchar('\n'); }
inline void rtt(int &x,int fl){//旋转
int s=nt[x].to[fl];
nt[x].to[fl]=nt[s].to[fl^1],nt[s].to[fl^1]=x,nt[s].sz=nt[x].sz;
nt[x].sz=nt[nt[x].to[0]].sz+nt[nt[x].to[1]].sz+nt[x].p;
x=s;
}
void nsrt(int &x,int w){//插入
if (!x){
nt[x=++nd].w=w,nt[x].p=nt[x].sz=1,nt[x].rnd=rand(); return;
}
nt[x].sz++;
if (nt[x].w==w) nt[x].p++;
else{
int flag=nt[x].w<w; nsrt(nt[x].to[flag],w);
if (nt[nt[x].to[flag]].rnd<nt[x].rnd) rtt(x,flag);
}
}
void dlt(int &x,int w){//删除
if (!x) return;
if (nt[x].w==w){
if (nt[x].p>1) { nt[x].sz--,nt[x].p--; return; }
if (!nt[x].to[0]){ x=nt[x].to[1]; return; }
if (!nt[x].to[1]){ x=nt[x].to[0]; return; }
int flag=nt[nt[x].to[0]].rnd<nt[nt[x].to[1]].rnd;
rtt(x,flag^1),dlt(x,w);
}
else nt[x].sz--,dlt(nt[x].to[nt[x].w<w],w);
}
void build(int x,int l,int r,int p,int w){//建树
nsrt(t[x].rt,w),t[x].l=l,t[x].r=r;//路径上的每个线段都要插入这个数
if (l==r) return; int mid=l+r>>1;
if (p<=mid) build(x<<1,l,mid,p,w);
else build(x<<1|1,mid+1,r,p,w);
}
void mdfy(int x,int p,int w,int pre){//修改
dlt(t[x].rt,pre),nsrt(t[x].rt,w);//删去旧节点,插入新节点
if (t[x].l==t[x].r) return; int mid=t[x].l+t[x].r>>1;
if (p<=mid) mdfy(x<<1,p,w,pre); else mdfy(x<<1|1,p,w,pre);
}
int rk(int x,int w){//查询排名
if (!x) return 0; int p=nt[x].to[0];
if (nt[x].w==w) return nt[p].sz;
else if (nt[x].w>w) return rk(p,w);
else return nt[p].sz+nt[x].p+rk(nt[x].to[1],w);
}
int srch_rk(int x,int l,int r,int w){//区间对应
if (t[x].l>r||t[x].r<l) return 0;
if (t[x].l>=l&&t[x].r<=r) return rk(t[x].rt,w);
return srch_rk(x<<1,l,r,w)+srch_rk(x<<1|1,l,r,w);
}
inline int srch_nm(int x,int y,int z){//查询已知排名的数
//这里比较特殊,因为每个区间是独立的,无法相加,所以我们需要二分来确定
int l=0,r=inf-1e9,ans;
while (l<=r){
int mid=l+r>>1;
if (srch_rk(1,x,y,mid)+1<=z) l=mid+1,ans=mid;
else r=mid-1;
}
return ans;
}
int frnt(int x,int w){//查询前驱
if (!x) return -inf;
if (nt[x].w<w){
int k=frnt(nt[x].to[1],w);
if (k!=-inf) return k; else return nt[x].w;
}
else return frnt(nt[x].to[0],w);
}
int srch_frnt(int x,int l,int r,int w){//区间对应
if (t[x].l>r||t[x].r<l) return -inf;
if (t[x].l>=l&&t[x].r<=r) return frnt(t[x].rt,w);
return max(srch_frnt(x<<1,l,r,w),srch_frnt(x<<1|1,l,r,w));
}
int bck(int x,int w){//查询后继
if (!x) return inf;
if (nt[x].w>w){
int k=bck(nt[x].to[0],w);
if (k!=inf) return k; else return nt[x].w;
}
else return bck(nt[x].to[1],w);
}
int srch_bck(int x,int l,int r,int w){//区间对应
if (t[x].l>r||t[x].r<l) return inf;
if (t[x].l>=l&&t[x].r<=r) return bck(t[x].rt,w);
return min(srch_bck(x<<1,l,r,w),srch_bck(x<<1|1,l,r,w));
}
int main(){
n=_read(),m=_read();
for (int i=1;i<=n;i++) build(1,1,n,i,a[i]=_read());
for (int i=1;i<=m;i++){
int flag=_read(),x=_read(),y=_read();
switch (flag){
case 1: write(srch_rk(1,x,y,_read())+1); break;
case 2: write(srch_nm(x,y,_read())); break;
case 3: mdfy(1,x,y,a[x]),a[x]=y; break;
case 4: write(srch_frnt(1,x,y,_read())); break;
case 5: write(srch_bck(1,x,y,_read())); break;
}
}
}