【题解】[SCOI2014]方伯伯的OJ FHQ_Treap

Problem

\(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;
}
posted @ 2021-06-22 21:44  Refined_heart  阅读(70)  评论(1编辑  收藏  举报