【题解】[SCOI2014]方伯伯的OJ FHQ_Treap
\(Solution:\)
这题看起来和 ZJOI 那个书架很像叭,但是它的数据范围不友好。
于是引入一个思路:将没有访问过的点合并成一段。这也意味着,树上每个节点中的排名和编号均是递增的。
考虑一下发现,我们需要维护一个编号对应的排名(用 map ),还需要排名对应的编号。
于是,考虑用\(\text{ mp[x] }\)表示一个编号区间从\(x\)开始的树上节点的编号。同时,树上结点维护一个节点上编号的范围(l,r数组),siz定义为区间的长度。
那么对于第一个操作:我们在 map 上面以第一关键字进行lower_bound(由于要避免点恰好在区间左端的情况,需要将编号+1再向左平移一位得到正确区间)
那么这样就得到了编号\(x\)在树上的节点编号。那么它的位置就显然是:该节点的排名加上它与区间右端点编号的差。(因为该节点的排名是计算的右端点排名)
之后,我们将编号 x 的位置替换成编号 y ,将原区间\([l,x,r]\)分成\([l,x-1],[y,y],[x+1,r]\)至于两端的那两个区间有没有,需要对x的位置特判一下。
实现一个删除区间的函数即可完成。
第二个操作和第三个操作:找编号的过程和第一个一样,那么修改的过程:将序列原区间分成\([L,x-1],[x+1,R],\)位置的话由于删掉了 x 所以需要注意应该直接接在 x 的位置上。
而将\(x\)置顶或置底,那就是\(\text{Ins(1,x,x) or Ins(n,x,x)}\)这种了。
第四个操作:考虑一般的 kth 函数写法,当找到对应点的时候我们可以直接返回它的编号:用左区间的编号加上当前找的点排名与左端点的排名差即可。
到此此题得解。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+10;
int tr[MAXN][2],siz[MAXN],pa[MAXN];
int cv[MAXN],l[MAXN],r[MAXN],cnt,rt,n,m;
inline int read(){
int s=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){
s=s*10-48+ch;
ch=getchar();
}
return s;
}
map<int,int>mp;
inline int rd(){
return rand()<<15|rand();
}
inline void pushup(int x){
siz[x]=siz[tr[x][0]]+siz[tr[x][1]]+r[x]-l[x]+1;
if(tr[x][0])pa[tr[x][0]]=x;
if(tr[x][1])pa[tr[x][1]]=x;
}
int build(int ll,int rl){
siz[++cnt]=rl-ll+1;
l[cnt]=ll;r[cnt]=rl;
mp[ll]=cnt;/////////////////////////////这里记录的是区间的左端点!!!
cv[cnt]=rd();
return cnt;
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(cv[x]<cv[y]){
tr[x][1]=merge(tr[x][1],y);
pushup(x);return x;
}
else{
tr[y][0]=merge(x,tr[y][0]);
pushup(y);return y;
}
}
void split(int now,int pos,int &x,int &y){
if(!now){x=y=0;return;}
if(pos<=siz[tr[now][0]])y=now,split(tr[now][0],pos,x,tr[now][0]);
else x=now,split(tr[now][1],pos-siz[tr[now][0]]-(r[now]-l[now]+1),tr[now][1],y);
pushup(now);
}
int kth(int now,int k){
if(k<=siz[tr[now][0]])return kth(tr[now][0],k);
k-=siz[tr[now][0]];
if(k-r[now]+l[now]-1<=0)return l[now]+k-1;//注意这里直接通过排名和编号连续的性质即可实现
return kth(tr[now][1],k-(r[now]-l[now]+1));
}
int getpos(int x){
int res=siz[tr[x][0]]+r[x]-l[x]+1;
while(pa[x]){
if(x==tr[pa[x]][1])res+=siz[tr[pa[x]][0]]+r[pa[x]]-l[pa[x]]+1;
x=pa[x];
}
return res;
}
void Ins(int pos,int l,int r){
int x,y;
split(rt,pos-1,x,y);
rt=merge(merge(x,build(l,r)),y);
}
void del(int l,int r){
int x,y,z;
split(rt,r,x,z);
split(x,l-1,x,y);
rt=merge(x,z);
}
int lastans;
int main(){
n=read(),m=read();
mp[1]=1;
Ins(1,1,n);
for(int i=1;i<=m;++i){
int opt,x,y;
opt=read();
x=read();
x-=lastans;
if(opt==1){
y=read();
y-=lastans;
int L=(--mp.lower_bound(x+1))->first;
//mp是区间编号最左端所对应的点,所以用lwer找到左边的mp即可
//mp替代了一颗平衡树的位置,它以编号为关键字,所以它的作用是很大的:维护编号所对的区间编号(通过lowerbound找区间左端点即可)
int pos=mp[L];
int u=getpos(pos);
int R=r[pos];
lastans=u-(r[pos]-x);//l,r数组维护编号 这里求位置 u是右边端点的位置因为它加上了当前区间的siz
printf("%d\n",lastans);
del(lastans-x+L,lastans-x+R);//删掉原来编号的区间 (树上rk对应的节点) x,R都是编号,而lastans是排名或说位置
if(x>L)Ins(lastans-x+L,L,x-1);//分类一下看看存不存在这个区间
Ins(lastans,y,y);
if(x<R)Ins(lastans+1,x+1,R);
}
else if(opt==2){
int L=(--mp.lower_bound(x+1))->first;
int pos=mp[L];
int u=getpos(pos);
int R=r[pos];
lastans=u-(r[pos]-x);
printf("%d\n",lastans);
del(lastans-x+L,lastans-x+R);
if(x>L)Ins(lastans-x+L,L,x-1);
if(x<R)Ins(lastans,x+1,R);//这个地方x的位置变了 所以直接从lastans加入就行了
Ins(1,x,x);//注意这些操作已经完成了置顶,不需要在前面给它分裂又合并什么的了,那样没有办法维护好编号
}
else if(opt==3){
int L=(--mp.lower_bound(x+1))->first;
int pos=mp[L];
int u=getpos(pos);
int R=r[pos];
lastans=u-(r[pos]-x);
printf("%d\n",lastans);
del(lastans-x+L,lastans-x+R);
if(x>L)Ins(lastans-x+L,L,x-1);
if(x<R)Ins(lastans,x+1,R);
Ins(n,x,x);//和2同理
}
else{
lastans=kth(rt,x);
printf("%d\n",lastans);
}
}
return 0;
}