洛谷 P3721 - [AH2017/HNOI2017]单旋(LCT)
终于调出来这道题了,写篇题解(
首先碰到这样的题我们肯定要考虑每种操作会对树的形态产生怎样的影响:
插入操作:对于 BST 有一个性质是,当你插入一个节点时,其在 BST 上的父亲肯定是,你把 BST 中父亲按权值 sort 一遍排成一列后,在待插入的数的两侧的数对应的节点中,深度较大者。因此我们考虑用一个
set,将所有点的权值和编号压进去然后在里面lower_bound即可找出待插入点两侧的点。单旋最小值:稍微画几个图即可发现,对于最小值代表的点 \(x\),如果 \(x\) 已经是根了就可以忽略此次操作,否则假设 \(x\) 在 splay 上的父亲为 \(f\),右儿子为 \(son\),原来的根为 \(rt\),那么此次操作等价于以下四个删断边操作:
- 断开 \(x,f\) 之间的边
- 断开 \(x,son\) 之间的边(如果 \(x\) 不存在右儿子则忽略)
- 连上 \(x,rt\) 之间的边,其中 \(x\) 为 \(rt\) 的父亲
- 连上 \(f,son\) 之间的边
注意到这里涉及删断边,并且在任意时刻图都是一棵森林,因此可以 LCT 维护。
单旋最大值:同单旋最小值的情况,只不过这里需要把右儿子改为左儿子
单旋删除最小值:与单旋最小值的情况类似,只不过这次不需要连 \(x\) 与 \(rt\) 之间的边
单旋删除最大值:与单旋删除最小值的情况类似,只不过这里也需要把右儿子改为左儿子
程序的大致框架构建出来了,接下来考虑如何具体实现每个操作:
- 查询一个点在 BST 上的深度:直接把这个点
access一遍并转到 splay 的根,那么这个点的siz就是该点在 BST 上的深度大小。 - 查询一个点的左/右儿子:在我们 LCT 的过程中,我们失去了原 BST 上左右儿子的信息,因此我们无法直接通过将它转到根,然后调用其
ch[0]/ch[1]的方法求其左右儿子。不过注意到每个点在 BST 上儿子个数 \(\le 2\),因此我们考虑 top tree 的思想,用一个set维护其虚儿子,这样我们每次查询一个点的左右儿子时,只需把它access一遍并转到根,然后在它的虚儿子集合中找到键值大于 / 小于该点的键值的点即可。
最后是一些注意点:
- 在
rotate时,如果 \(x\) 的父亲是 \(x\) 所在splay的根,那么我们要在 \(x\) 父亲的父亲的虚儿子集合中删除 \(y\) 加入 \(x\),这一点在普通的 top tree 中不用考虑,因为转 \(x\) 不会影响 \(x\) 的父亲的父亲的子树的大小,但是这里我们维护的是一个点的虚儿子具体是什么,虚儿子变了,父亲的信息也要改变。 - 在查询左右儿子时,不能找到一个键值比待查询点键值大 / 小的点就
return,要在对应子树中找到深度最浅(中序遍历中第一位)的点再返回。
const int MAXN=1e5;
const int INF=0x3f3f3f3f;
int ncnt=0;
struct node{int ch[2],f,siz,rev_lz,val;set<int> img_ch;} s[MAXN+5];
void pushup(int k){s[k].siz=s[s[k].ch[0]].siz+s[s[k].ch[1]].siz+1;}
int ident(int k){return ((s[s[k].f].ch[0]==k)?0:((s[s[k].f].ch[1]==k)?1:-1));}
void connect(int k,int f,int op){s[k].f=f;if(~op) s[f].ch[op]=k;}
void rotate(int x){
int y=s[x].f,z=s[y].f,dx=ident(x),dy=ident(y);
connect(s[x].ch[dx^1],y,dx);connect(y,x,dx^1);connect(x,z,dy);
pushup(y);pushup(x);assert(~dx);
if(dy==-1&&z){
s[z].img_ch.erase(s[z].img_ch.find(y));
s[z].img_ch.insert(x);
}
}
void splay(int k){
while(~ident(k)){
if(ident(s[k].f)==-1) rotate(k);
else if(ident(k)==ident(s[k].f)) rotate(s[k].f),rotate(k);
else rotate(k),rotate(k);
}
}
void access(int k){
int pre=0;
for(;k;pre=k,k=s[k].f){
splay(k);
if(s[k].ch[1]) s[k].img_ch.insert(s[k].ch[1]);s[k].ch[1]=pre;
if(s[k].ch[1]) s[k].img_ch.erase(s[k].img_ch.find(s[k].ch[1]));
pushup(k);
}
}
int findroot(int k){
access(k);splay(k);
while(s[k].ch[0]) k=s[k].ch[0];
splay(k);return k;
}
void link(int x,int y){
access(x);splay(x);
s[x].f=y;s[y].img_ch.insert(x);
}//y is x's father
int getfa(int x){
access(x);splay(x);x=s[x].ch[0];
while(s[x].ch[1]) x=s[x].ch[1];
return x;
}
int getls(int x){
access(x);splay(x);
for(int c:s[x].img_ch) if(s[c].val<s[x].val){
while(s[c].ch[0]) c=s[c].ch[0];
return c;
}
return 0;
}
int getrs(int x){
access(x);splay(x);
for(int c:s[x].img_ch) if(s[c].val>s[x].val){
while(s[c].ch[0]) c=s[c].ch[0];
return c;
}
return 0;
}
void cut(int x,int y){
access(x);splay(x);int son=s[x].ch[0];
s[x].ch[0]=s[son].f=0;pushup(x);
}//y is x's father
set<pii> st;
int calc_dep(int x){access(x);splay(x);return s[x].siz;}
void splay_mn(){
pii p=*++st.begin();int id=p.se;
access(id);splay(id);printf("%d\n",s[id].siz);
if(findroot(id)==id) return;
int fa=getfa(id),rt=findroot(id);
cut(id,fa);int son=getrs(id);
if(son) assert(getfa(son)==id),cut(son,id),link(son,fa);
link(rt,id);assert(findroot(fa)==id);
}
void splay_mx(){
pii p=*-- --st.end();int id=p.se;
access(id);splay(id);printf("%d\n",s[id].siz);
if(findroot(id)==id) return;
int fa=getfa(id),rt=findroot(id);
cut(id,fa);int son=getls(id);
if(son) assert(getfa(son)==id),cut(son,id),link(son,fa);
link(rt,id);
}
void del_mn(){
pii p=*++st.begin();int id=p.se;st.erase(st.find(p));
access(id);splay(id);printf("%d\n",s[id].siz);
if(findroot(id)==id){
int son=getrs(id);
if(son) cut(son,id);
return;
}
int fa=getfa(id),rt=findroot(id);
cut(id,fa);int son=getrs(id);
if(son) assert(getfa(son)==id),cut(son,id),link(son,fa);
}
void del_mx(){
pii p=*-- --st.end();int id=p.se;st.erase(st.find(p));
access(id);splay(id);printf("%d\n",s[id].siz);
if(findroot(id)==id){
int son=getls(id);
if(son) cut(son,id);
return;
}
int fa=getfa(id),rt=findroot(id);
cut(id,fa);int son=getls(id);
if(son) assert(getfa(son)==id),cut(son,id),link(son,fa);
}
int main(){
int qu;scanf("%d",&qu);
st.insert(mp(0,0));st.insert(mp(INF,0));
while(qu--){
int opt;scanf("%d",&opt);
if(opt==1){
int x;scanf("%d",&x);
s[++ncnt].val=x;s[ncnt].siz=1;
st.insert(mp(x,ncnt));
if(st.size()>3){
pii nxt=*st.upper_bound(mp(x,ncnt));
pii pre=*--st.lower_bound(mp(x,ncnt));
int L=(pre.se)?calc_dep(pre.se):0;
int R=(nxt.se)?calc_dep(nxt.se):0;
if(L>R) link(ncnt,pre.se);
else link(ncnt,nxt.se);
} printf("%d\n",calc_dep(ncnt));
} else if(opt==2) splay_mn();
else if(opt==3) splay_mx();
else if(opt==4) del_mn();
else del_mx();
}
return 0;
}
洛谷 P3721 - [AH2017/HNOI2017]单旋(LCT)的更多相关文章
- 洛谷P3721 [AH2017/HNOI2017]单旋(线段树 set spaly)
题意 题目链接 Sol 这题好毒瘤啊.. 首先要观察到几个性质: 将最小值旋转到根相当于把右子树变为祖先的左子树,然后将原来的根变为当前最小值 上述操作对深度的影响相当于右子树不变,其他的位置-1 然 ...
- P3721 [AH2017/HNOI2017]单旋
题目:https://www.luogu.org/problemnew/show/P3721 手玩一下即可AC此题. 结论:插入x后,x要么会成为x的前驱的右儿子,要么成为x的后继的左儿子,这取决于它 ...
- luogu P3721 [AH2017/HNOI2017]单旋
传送门 \(Spaly:\)??? 考虑在暴力模拟的基础上优化 如果要插入一个数,那么根据二叉查找树的性质,这个点一定插在他的前驱的右子树或者是后继的左子树,可以利用set维护当前树里面的数,方便查找 ...
- bzoj 4825: [Hnoi2017]单旋 [lct]
4825: [Hnoi2017]单旋 题意:有趣的spaly hnoi2017刚出来我就去做,当时这题作死用了ett,调了5节课没做出来然后发现好像直接用lct就行了然后弃掉了... md用lct不知 ...
- 洛谷 P3723 [AH2017/HNOI2017]礼物 解题报告
P3723 [AH2017/HNOI2017]礼物 题目描述 我的室友最近喜欢上了一个可爱的小女生.马上就要到她的生日了,他决定买一对情侣手环,一个留给自己,一个送给她.每个手环上各有 \(n\) 个 ...
- [AH2017/HNOI2017]单旋
题目 \(\rm splay\)水平太差,于是得手玩一下才能发现规律 首先插入一个数,其肯定会成为其前驱的右儿子或者是后继的左儿子,进一步手玩发现前驱的右儿子或者是后继的左儿子一定只有一个是空的,我们 ...
- 洛谷P3722 [AH2017/HNOI2017]影魔(线段树)
题意 题目链接 Sol 题解好神仙啊qwq. 一般看到这种考虑最大值的贡献的题目不难想到单调数据结构 对于本题而言,我们可以预处理出每个位置左边第一个比他大的位置\(l_i\)以及右边第一个比他大的位 ...
- 洛谷P3726 [AH2017/HNOI2017]抛硬币(组合数+扩展Lucas)
题面 传送门 题解 果然--扩展\(Lucas\)学了跟没学一样-- 我们先考虑\(a=b\)的情况,这种情况下每一个\(A\)胜的方案中\(A\)和\(B\)的所有位上一起取反一定是一个\(A\)败 ...
- 洛谷P3724 [AH2017/HNOI2017]大佬(决策单调性)
传送门 这个思路很妙诶->这里 以下为了方便,我把自信说成血量好了 虽然表面上看起来每一天有很多种选择,然而我们首先要保证的是不死,然后考虑不死的情况下最多能拿出多少天来进行其他操作.不死可以d ...
随机推荐
- Fikker 管理平台弱口令
官网:www.fikker.com 应用介绍:Fikker 是一款面向 CDN/站长 的专业级网站缓存(Webcache)和反向代理服务器软件(Reverse Proxy Server). 发现过程: ...
- 【错误分析】NX error status: 32
在进行NX 制图里面的表格注释合并单元格时,总是报错NX error status: 32,找了很久都不知道问题所在. 报错提示如下: NXOpen.NXException: NX error sta ...
- 虚拟机研究系列-「GC本质底层机制」SafePoint的深入分析和底层原理探究指南
SafePoint前提介绍 在高度优化的现代JVM里,Safepoint有几种不同的用法.GC safepoint是最常见.大家听说得最多的,但还有deoptimization safepoint也很 ...
- Scrum Meeting 14
第14次例会报告 日期:2021年06月07日 会议主要内容概述: 汇报了已完成的工作,明确了下一步目标,正在努力赶进度. 一.进度情况 我们采用日报的形式记录每个人的具体进度,链接Home · Wi ...
- 第4次 Beta Scrum Meeting
本次会议为Beta阶段第4次Scrum Meeting会议 会议概要 会议时间:2021年6月4日 会议地点:「腾讯会议」线上进行 会议时长:0.5小时 会议内容简介:对完成工作进行阶段性汇报:对下一 ...
- OO第二单元电梯作业总结
目录 目录一.第一次作业分析设计策略基于度量分析程序结构二.第二次作业分析设计策略基于度量分析程序结构三.第三次作业分析设计策略基于度量分析程序结构四.分析自己程序的bug五.发现别人程序bug所采用 ...
- 人人都写过的5个Bug!
大家好,我是良许. 计算机专业的小伙伴,在学校期间一定学过 C 语言.它是众多高级语言的鼻祖,深入学习这门语言会对计算机原理.操作系统.内存管理等等底层相关的知识会有更深入的了解,所以我在直播的时候, ...
- 2021.9.22考试总结[NOIP模拟59]
T1 柱状图 关于每个点可以作出两条斜率绝对值为\(1\)的直线. 将绝对值拆开,对在\(i\)左边的点\(j\),\(h_i-i=h_j-j\),右边则是把减号换成加号. 把每个点位置为横坐标,高度 ...
- 2021.8.13考试总结[NOIP模拟38]
T1 a 入阵曲.枚举矩形上下界,之后从左到右扫一遍.用树状数组维护前缀和加特判可以$A$,更保险要脸的做法是双指针扫,因为前缀和单调不减. $code:$ 1 #include<bits/st ...
- 洛谷 P3195 [HNOI2008] 玩具装箱
链接: P3195 题意: 给出 \(n\) 个物品及其权值 \(c\),连续的物品可以放进一个容器,如果将 \(i\sim j\) 的物品放进一个容器,产生的费用是 \(\left(j-i+\sum ...