洛谷P3285 [SCOI2014]方伯伯的OJ 动态开点平衡树

洛谷P3285 [SCOI2014]方伯伯的OJ 动态开点平衡树

题目描述#

方伯伯正在做他的 Oj 。现在他在处理 Oj 上的用户排名问题。 Oj 上注册了 n 个用户,编号为 1n,一开始他们按照编号排名。

方伯伯会按照心情对这些用户做以下四种操作,修改用户的排名和编号:

1.操作格式为 1 x y,意味着将编号为x 的用户编号改为 y ,而排名不变,执行完该操作后需要输出该用户在队列中的位置,数据保证 x 必然出现在队列中,同时,y 是一个当前不在排名中的编号。

2.操作格式为2 x,意味着将编号为 x 的用户的排名提升到第一位,执行完该操作后需要输出执行该操作前编号为 x 用户的排名。

3.操作格式为 3 x ,意味着将编号为 x 的用户的排名降到最后一位,执行完该操作后需要输出执行该操作前编号为 x 用户的排名。

4.操作格式为 4 k,意味着查询当前排名为 k 的用户编号,执行完该操作后需要输出当前操作用户的编号。

但同时为了防止别人监听自己的工作,方伯伯对他的操作进行了加密,即将四种操作的格式分别改为了:

  • 1 x+a y+a
  • 2 x+a
  • 3 x+a
  • 4 k+a

其中 a 为上一次操作得到的输出,一开始 a=0

例如:上一次操作得到的输出是 5 这一次操作的输入为:1 13 15

因为这个输入是经过加密后的,所以你应该处理的操作是 1 8 10

现在你截获了方伯伯的所有操作,希望你能给出结果。

输入输出格式#

输入格式#

输入的第 1 行包含 2 个用空格分隔的整数 nm ,表示初始用户数和操作数。此后有 m 行,每行是一个询问,询问格式如上所示。

输出格式#

输出包含 m 行。每行包含一个整数,其中第 i 行的整数表示第 i 个操作的输出。

输入输出样例#

输入样例 #1#

10 10
1 2 11
3 13
2 5
3 7
2 8
2 10
2 11
3 14
2 18
4 9

输出样例 #1#

2
2
2
4
3
5
5
7
8
11

说明#

对于 100% 的数据,1n1081m105

输入保证对于所有的操作 123x 必然已经出现在队列中,同时对于所有操作 11<=y<=2×108,并且 y 没有出现在队列中。

对于所有操作 4,保证 1kn

分析#

无旋 treap 按照 siz 分裂可以维护序列上的信息

暴力的做法是把 1n 中的每一个数都扔到平衡树中

对于每一个数,用一个数组记录一下它在平衡树上对应的是哪一个节点

对于操作 1,只需要把 x 在平衡树上所代表的节点分裂出来,然后把当前节点的权值改为 y,再合并回去即可

对于操作 2,通过一个函数 getrk(x) 查找平衡树上编号为 x 的节点在序列中的位置

具体的实现就是一直跳父亲,如果当前节点是父亲节点的右儿子,那么将左儿子和父亲节点的 siz 累加

父亲的信息在 pushup 的时候更新,而且在进入 mergesplit 的时候要把当前节点的父亲节点置为 0

找到排名之后把当前的节点和排名在它前面的节点都 split 出来,交换一下位置再合并回去即可

操作 3 同理

对于操作 4,从根节点开始在平衡树上二分查找即可

但是 n 的范围达到了 1e8,这样做复杂度肯定不对

发现 m 的范围比较小

也就是说有一些位置是不会被操作的,而且这样的位置很多

所以我们可以改变平衡树中维护的信息

将维护一个节点改为维护一个区间,可以理解为动态开点

节点上要多维护区间的左右端点以及长度

当对于某一个点进行操作时,就把当前节点所在的区间分成几个小区间扔进平衡树中

同时要开一个 map 记录一下每一个节点掌管的是哪一个区间

方便对于一个数快速查询它被哪一个节点所掌管

具体实现的过程中还是有很多细节的

代码#

复制#include<cstdio>
#include<algorithm>
#include<map>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e6+5;
int cnt,rt,rt1,rt2,rt3,rt4,n,m;
std::map<int,int> mp;
#define mit std::map<int,int>::iterator
struct trr{
	int ch[2],siz,rd,fa,l,r,len;
}tr[maxn];
void push_up(rg int da){
	tr[da].siz=tr[tr[da].ch[0]].siz+tr[tr[da].ch[1]].siz+tr[da].len;
	tr[tr[da].ch[0]].fa=da,tr[tr[da].ch[1]].fa=da;
}
int ad(rg int l,rg int r){
	tr[++cnt].rd=rand();
	mp[r]=cnt;
	tr[cnt].l=l;
	tr[cnt].r=r;
	tr[cnt].len=r-l+1;
	tr[cnt].siz=tr[cnt].len;
	return cnt;
}
void split(rg int now,rg int val,rg int& x,rg int& y){
	tr[now].fa=0;
	if(!now){
		x=y=0;
		return;
	}
	if(tr[tr[now].ch[0]].siz+tr[now].len<=val){
		x=now;
		split(tr[now].ch[1],val-tr[tr[now].ch[0]].siz-tr[now].len,tr[now].ch[1],y);
	} else {
		y=now;
		split(tr[now].ch[0],val,x,tr[now].ch[0]);
	}
	push_up(now);
}
int bing(rg int aa,rg int bb){
	tr[aa].fa=0,tr[bb].fa=0;
	if(!aa || !bb) return aa+bb;
	if(tr[aa].rd<tr[bb].rd){
		tr[aa].ch[1]=bing(tr[aa].ch[1],bb);
		push_up(aa);
		return aa;
	} else {
		tr[bb].ch[0]=bing(aa,tr[bb].ch[0]);
		push_up(bb);
		return bb;
	}
}
int getrk(rg int da){
	rg int nans=tr[tr[da].ch[0]].siz+tr[da].len;
	while(tr[da].fa){
		if(tr[tr[da].fa].ch[1]==da){
			da=tr[da].fa;
			nans+=tr[tr[da].ch[0]].siz+tr[da].len;
		} else {
			da=tr[da].fa;
		}
	}
	return nans;
}
int kth(rg int da,rg int k){
	while(1){
		if(k>tr[tr[da].ch[0]].siz+tr[da].len){
			k-=tr[tr[da].ch[0]].siz+tr[da].len;
			da=tr[da].ch[1];
		} else if(k<=tr[tr[da].ch[0]].siz+tr[da].len && k>=tr[tr[da].ch[0]].siz+1){
			return da;
		} else {
			da=tr[da].ch[0];
		}
	}
}
int latans;
int main(){
	srand(time(0));
	n=read(),m=read();
	rt=ad(1,n);
	rg int aa,bb,cc,dd,ee;
	rg mit it;
	for(rg int i=1;i<=m;i++){
		aa=read(),bb=read();
		bb-=latans;
		if(aa==1){
			cc=read();
			cc-=latans;
			it=mp.lower_bound(bb);
			dd=it->second;
			ee=getrk(dd);
			mp.erase(it);
			split(rt,ee-1,rt,rt1);
			split(rt1,tr[dd].len,rt1,rt2);
			if(tr[dd].l!=bb) rt=bing(rt,ad(tr[dd].l,bb-1));
			rt=bing(rt,ad(cc,cc));
			if(tr[dd].r!=bb) rt=bing(rt,ad(bb+1,tr[dd].r));
			rt=bing(rt,rt2);
			printf("%d\n",latans=getrk(mp[cc]));
		} else if(aa==2){
			it=mp.lower_bound(bb);
			dd=it->second;
			ee=getrk(dd);
			mp.erase(it);
			printf("%d\n",latans=ee-(tr[dd].r-bb));
			split(rt,ee-1,rt,rt1);
			split(rt1,tr[dd].len,rt1,rt2);
			rt=bing(ad(bb,bb),rt);
			if(tr[dd].l!=bb) rt=bing(rt,ad(tr[dd].l,bb-1));
			if(tr[dd].r!=bb) rt=bing(rt,ad(bb+1,tr[dd].r));
			rt=bing(rt,rt2);
		} else if(aa==3){
			it=mp.lower_bound(bb);
			dd=it->second;
			ee=getrk(dd);
			mp.erase(it);
			printf("%d\n",latans=ee-(tr[dd].r-bb));
			split(rt,ee-1,rt,rt1);
			split(rt1,tr[dd].len,rt1,rt2);
			if(tr[dd].l!=bb) rt=bing(rt,ad(tr[dd].l,bb-1));
			if(tr[dd].r!=bb) rt=bing(rt,ad(bb+1,tr[dd].r));
			rt=bing(rt,rt2);
			rt=bing(rt,ad(bb,bb));
		} else {
			dd=kth(rt,bb);
			ee=getrk(dd);
			printf("%d\n",latans=tr[dd].r-(ee-bb));
		}
	}
	return 0;
}
posted @   liuchanglc  阅读(166)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
点击右上角即可分享
微信分享提示
主题色彩