洛谷P3285 [SCOI2014]方伯伯的OJ 动态开点平衡树
洛谷P3285 [SCOI2014]方伯伯的OJ 动态开点平衡树
题目描述#
方伯伯正在做他的 。现在他在处理 上的用户排名问题。 上注册了 个用户,编号为 ,一开始他们按照编号排名。
方伯伯会按照心情对这些用户做以下四种操作,修改用户的排名和编号:
.操作格式为 ,意味着将编号为 的用户编号改为 ,而排名不变,执行完该操作后需要输出该用户在队列中的位置,数据保证 必然出现在队列中,同时, 是一个当前不在排名中的编号。
.操作格式为,意味着将编号为 的用户的排名提升到第一位,执行完该操作后需要输出执行该操作前编号为 用户的排名。
.操作格式为 ,意味着将编号为 的用户的排名降到最后一位,执行完该操作后需要输出执行该操作前编号为 用户的排名。
4.操作格式为 ,意味着查询当前排名为 的用户编号,执行完该操作后需要输出当前操作用户的编号。
但同时为了防止别人监听自己的工作,方伯伯对他的操作进行了加密,即将四种操作的格式分别改为了:
其中 为上一次操作得到的输出,一开始 。
例如:上一次操作得到的输出是 这一次操作的输入为:
因为这个输入是经过加密后的,所以你应该处理的操作是
现在你截获了方伯伯的所有操作,希望你能给出结果。
输入输出格式#
输入格式#
输入的第 行包含 个用空格分隔的整数 和 ,表示初始用户数和操作数。此后有 行,每行是一个询问,询问格式如上所示。
输出格式#
输出包含 行。每行包含一个整数,其中第 行的整数表示第 个操作的输出。
输入输出样例#
输入样例 #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
说明#
对于 的数据,,
输入保证对于所有的操作 ,,, 必然已经出现在队列中,同时对于所有操作 ,,并且 没有出现在队列中。
对于所有操作 ,保证 。
分析#
无旋 按照 分裂可以维护序列上的信息
暴力的做法是把 中的每一个数都扔到平衡树中
对于每一个数,用一个数组记录一下它在平衡树上对应的是哪一个节点
对于操作 ,只需要把 在平衡树上所代表的节点分裂出来,然后把当前节点的权值改为 ,再合并回去即可
对于操作 ,通过一个函数 查找平衡树上编号为 的节点在序列中的位置
具体的实现就是一直跳父亲,如果当前节点是父亲节点的右儿子,那么将左儿子和父亲节点的 累加
父亲的信息在 的时候更新,而且在进入 和 的时候要把当前节点的父亲节点置为
找到排名之后把当前的节点和排名在它前面的节点都 出来,交换一下位置再合并回去即可
操作 同理
对于操作 ,从根节点开始在平衡树上二分查找即可
但是 的范围达到了 ,这样做复杂度肯定不对
发现 的范围比较小
也就是说有一些位置是不会被操作的,而且这样的位置很多
所以我们可以改变平衡树中维护的信息
将维护一个节点改为维护一个区间,可以理解为动态开点
节点上要多维护区间的左右端点以及长度
当对于某一个点进行操作时,就把当前节点所在的区间分成几个小区间扔进平衡树中
同时要开一个 记录一下每一个节点掌管的是哪一个区间
方便对于一个数快速查询它被哪一个节点所掌管
具体实现的过程中还是有很多细节的
代码#
复制#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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爆火,是硬核还是营销?