在这里先给出一道模板题
【XSY2685】【LG3380】【BZOJ3196】【TYVJ1730】二逼平衡树
\(Description\)
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
1.查询k在区间内的排名(一个数的排名是小于这个数的个数+1)
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,108]\)
3.虽然原题没有,但事实上\(5\)操作的\(k\)可能为负数
4.保证答案一定存在
题解
一、这道题目在说什么?
给你一个有序序列,需要实现:
- 区间查询\(k\)排名
- 区间查询排名\(k\)的值
- 单点修改权值
- 区间查询\(k\)前驱
- 区间查询\(k\)后继
二、这道题目怎么做?
这道题,涉及到了区间查询以及单点修改,我们反应过来:这是一道待修区间K大的题目。
我们可爱的线段树能够实现单点修改和区间修改,但是面对区间K大就无能为力
我们精简的主席树和功能强大的平衡树可以完成区间K大的任务,但是不能完成待修的任务
于是我们不得不放弃普通的线段树或者平衡树做法,这时,就要用树套树做法了。
我们在外层建一棵线段树,完成单点修改的任务
再在每个线段树节点建一棵平衡树(可以是\(treap\),也可以是\(splay\),也可以是另外各种可以实现区间询问的平衡树)
每次区间询问的时候,就调用线段树节点中的平衡树完成查询,在这个时候,平衡树对应的就是一个区间,而不是一整个线段树
在这里我用的是\(fhq\) \(treap\),个人认为\(fhq\) \(treap\)容易理解,好打
在这里就不多讲\(fhq\) \(treap\) 的代码部分,都是板子,就只讲线段树的部分
为了方便,我将\(fhq\) \(treap\)封装成一个\(struct\),需要的可以看看
三、代码实现
1. 区间查询值\(k\)的排名
我们在线段树中找到在询问区间内的节点,然后调用平衡树查询在节点的平衡树中的\(k\)排名
然后我们考虑一下,如果每次都查询的是小于等于\(k\)的数的个数,那么合并的时候,可能会出现很多个等于\(k\)的值,也就是说,这样求出的排名可能会重复多出一些
于是我们将\(queryrank\)定义为寻找小于\(k\)的数的个数,在最后的时候再\(+1\)
在这里,我们看看线段树在遍历的时候的小细节,我们可以分为\(3\)类
- 当整个区间都在左子树,返回左子树的查询
- 当整个区间都在右子树,返回右子树的查询
- 区间横跨左右子树,返回两个子树的查询和,不过左子树的查询区间要变成\([ql,mid]\),右子树变成\([mid+1,qr]\)
int queryrank(int k,int l,int r,int ql,int qr,int val)//询问在[ql,qr]区间内小于val的数的个数 { if(ql<=l&&r<=qr)return a[k].rank(a[k].rt,val)-1;//在询问区间内,调用平衡树,记得-1 int ans=0; int mid=(l+r)>>1; if(qr<=mid)ans=queryrank(k<<1,l,mid,ql,qr,val);//整个区间都在左子树 else if(ql>mid)ans=queryrank(k<<1|1,mid+1,r,ql,qr,val);//整个区间都在右子树 else ans=queryrank(k<<1,l,mid,ql,mid,val)+queryrank(k<<1|1,mid+1,r,mid+1,qr,val);//横跨左右子树 return ans; }
2.查询排名为\(k\)的值
这个操作不能树套树中实现,因为不知道怎么将答案合并起来,于是我们考虑写一个简单的二分答案
每次二分一个值,用\(queryrank\)(楼上)来查询这个值的\(rank\)
//无脑二分不多解释 int queryval(int ql,int qr,int val)//询问在[ql,qr]区间内排名为val的值 { int l=0,r=1e8,ans=-1; while(l<=r) { int mid=(l+r)>>1; if(queryrank(1,1,n,ql,qr,mid)+1<=val)ans=mid,l=mid+1; else r=mid-1; } return ans; }
3.修改\(k\)位置上的值
我们从根往\([k,k]\)遍历,将路径上的节点中的平衡树中的\(k\)位置上的值先删除,然后再加入新的值,直到遍历到\([k,k]\),跟往常的线段树修改没什么区别
void change(int k,int l,int r,int pos,int val)//将pos位置的值改为val { a[k].del(a[k].rt,p[pos]);//删除pos位置的值 a[k].ins(a[k].rt,val);//加入val if(l==r)return ; int mid=(l+r)>>1; if(pos<=mid)change(k<<1,l,mid,pos,val); else change(k<<1|1,mid+1,r,pos,val); }
4. 查询\(k\)在区间内的前驱
查询前驱也没什么特别的,在询问区间范围内就调用平衡树,最后取左右子树查询的\(maxn\)
也可以像操作\(1\)那样分离
//不再注释 int querypre(int k,int l,int r,int ql,int qr,int val)//查询[ql,qr]区间内val的前驱 { if(l>=ql&&r<=qr)return a[k].pre(a[k].rt,val); int ans=-INF; int mid=(l+r)>>1; if(qr<=mid)ans=max(ans,querypre(k<<1,l,mid,ql,qr,val)); else if(ql>mid)ans=max(ans,querypre(k<<1|1,mid+1,r,ql,qr,val)); else ans=max(ans,max(querypre(k<<1,l,mid,ql,mid,val),querypre(k<<1|1,mid+1,r,mid+1,qr,val))); return ans; }
5.查询\(k\)在区间内的后继
同上,后继取左右子树查询的\(minn\)
int querynxt(int k,int l,int r,int ql,int qr,int val) { if(l>=ql&&r<=qr)return a[k].nxt(a[k].rt,val); int mid=(l+r)>>1; int ans=INF; if(qr<=mid)ans=min(ans,querynxt(k<<1,l,mid,ql,qr,val)); else if(ql>mid)ans=min(ans,querynxt(k<<1|1,mid+1,r,ql,qr,val)); else ans=min(ans,min(querynxt(k<<1,l,mid,ql,mid,val),querynxt(k<<1|1,mid+1,r,mid+1,qr,val))); return ans; }
代码:
#include<bits/stdc++.h> using namespace std; const int INF=0x7fffffff,N=5e4+10; int n,m; int p[N]; int cnt=0; struct tree { int ch[2],siz,val,rd; }t[N*40]; int seed=233; struct FhqTreap { int rt; int Rand() { return seed=int(seed*4827111l%0x7ffffffff); } int newnode(int val) { t[++cnt].val=val; t[cnt].siz=1; t[cnt].rd=Rand(); return cnt; } void up(int k) { t[k].siz=t[t[k].ch[0]].siz+t[t[k].ch[1]].siz+1; } void split(int now,int val,int &a,int &b) { if(!now) { a=b=0; return ; } if(t[now].val<=val) { a=now; split(t[now].ch[1],val,t[a].ch[1],b); } else { b=now; split(t[now].ch[0],val,a,t[b].ch[0]); } up(now); } int merge(int a,int b) { if(!(a&&b))return a+b; if(t[a].rd<t[b].rd) { t[a].ch[1]=merge(t[a].ch[1],b); up(a); return a; } else { t[b].ch[0]=merge(a,t[b].ch[0]); up(b); return b; } } void ins(int &rt,int val) { int a,b; int c=newnode(val); split(rt,val,a,b); rt=merge(merge(a,c),b); } void del(int &rt,int val) { int a,b,c; split(rt,val,a,c); split(a,val-1,a,b); b=merge(t[b].ch[0],t[b].ch[1]); a=merge(a,b); rt=merge(a,c); } int rank(int rt,int val) { int a,b; split(rt,val-1,a,b); int ans=t[a].siz+1; rt=merge(a,b); return ans; } int kth(int now,int rk) { while((t[t[now].ch[0]].siz+1)!=rk) { if(t[t[now].ch[0]].siz>=rk)now=t[now].ch[0]; else { rk-=t[t[now].ch[0]].siz+1; now=t[now].ch[1]; } } return t[now].val; } int pre(int rt,int val) { int a,b; split(rt,val-1,a,b); int ans=t[a].siz?kth(a,t[a].siz):-INF; rt=merge(a,b); return ans; } int nxt(int rt,int val) { int a,b; split(rt,val,a,b); int ans=t[b].siz>0?kth(b,1):INF; rt=merge(a,b); return ans; } }a[N<<2]; void build(int k,int l,int r) { for(int i=l;i<=r;i++)a[k].ins(a[k].rt,p[i]); if(l==r)return ; int mid=(l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r); } int queryrank(int k,int l,int r,int ql,int qr,int val) { if(ql<=l&&r<=qr)return a[k].rank(a[k].rt,val)-1; int ans=0; int mid=(l+r)>>1; if(qr<=mid)ans=queryrank(k<<1,l,mid,ql,qr,val); else if(ql>mid)ans=queryrank(k<<1|1,mid+1,r,ql,qr,val); else ans=queryrank(k<<1,l,mid,ql,mid,val)+queryrank(k<<1|1,mid+1,r,mid+1,qr,val); return ans; } int queryval(int ql,int qr,int val) { int l=0,r=1e8,ans=-1; while(l<=r) { int mid=(l+r)>>1; if(queryrank(1,1,n,ql,qr,mid)+1<=val)ans=mid,l=mid+1; else r=mid-1; } return ans; } void change(int k,int l,int r,int pos,int val) { a[k].del(a[k].rt,p[pos]); a[k].ins(a[k].rt,val); if(l==r)return ; int mid=(l+r)>>1; if(pos<=mid)change(k<<1,l,mid,pos,val); else change(k<<1|1,mid+1,r,pos,val); } int querypre(int k,int l,int r,int ql,int qr,int val) { if(l>=ql&&r<=qr)return a[k].pre(a[k].rt,val); int ans=-INF; int mid=(l+r)>>1; if(qr<=mid)ans=max(ans,querypre(k<<1,l,mid,ql,qr,val)); else if(ql>mid)ans=max(ans,querypre(k<<1|1,mid+1,r,ql,qr,val)); else ans=max(ans,max(querypre(k<<1,l,mid,ql,mid,val),querypre(k<<1|1,mid+1,r,mid+1,qr,val))); return ans; } int querynxt(int k,int l,int r,int ql,int qr,int val) { if(l>=ql&&r<=qr)return a[k].nxt(a[k].rt,val); int mid=(l+r)>>1,ans=INF; if(qr<=mid)ans=min(ans,querynxt(k<<1,l,mid,ql,qr,val)); else if(ql>mid)ans=min(ans,querynxt(k<<1|1,mid+1,r,ql,qr,val)); else ans=min(ans,min(querynxt(k<<1,l,mid,ql,mid,val),querynxt(k<<1|1,mid+1,r,mid+1,qr,val))); return ans; } inline int read() { int x=0,f=1; char ch=getchar(); while(!isdigit(ch)) { if(ch=='-')f=-1; ch=getchar(); } while(isdigit(ch)) { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); } return x*f; } int main() { srand(19260817); n=read(),m=read(); for(int i=1;i<=n;i++)p[i]=read(); build(1,1,n); int op,l,r,k,pos; for(int i=1;i<=m;i++) { op=read(); if(op==1) { l=read(),r=read(),k=read(); printf("%d\n",queryrank(1,1,n,l,r,k)+1); } if(op==2) { l=read(),r=read(),k=read(); printf("%d\n",queryval(l,r,k)); } if(op==3) { pos=read(),k=read(); change(1,1,n,pos,k); p[pos]=k; } if(op==4) { l=read(),r=read(),k=read(); printf("%d\n",querypre(1,1,n,l,r,k)); } if(op==5) { l=read(),r=read(),k=read(); printf("%d\n",querynxt(1,1,n,l,r,k)); } } return 0; } /* 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 */