Description

\(H\) 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构。伸展树(\(splay\))是一种数据

结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 \(H\) 国的必修技能。有一天,邪恶的“卡”带着

他的邪恶的“常数”来企图毁灭 \(H\) 国。“卡”给 \(H\) 国的人洗脑说,\(splay\) 如果写成单旋的,将会更快。“卡”称

“单旋 \(splay\) ”为“ \(spaly\) ”。虽说他说的很没道理,但还是有 \(H\) 国的人相信了,小 \(H\) 就是其中之一,\(spaly\) 马

上成为他的信仰。 而 \(H\) 国的国王,自然不允许这样的风气蔓延,国王构造了一组数据,数据由 \(m\) 个操作构成,

他知道这样的数据肯定打垮 \(spaly\),但是国王还有很多很多其他的事情要做,所以统计每个操作所需要的实际代价

的任务就交给你啦。

数据中的操作分为五种:

  1. 插入操作:向当前非空 \(spaly\) 中插入一个关键码为 \(key\) 的新孤立节点。插入方法为,先让 \(key\) 和根比较,如果

    \(key\) 比根小,则往左子树走,否则往右子树走,如此反复,直到某个时刻,\(key\) 比当前子树根 \(x\) 小,而 \(x\) 的左子

    树为空,那就让 \(key\) 成为 \(x\) 的左孩子; 或者 \(key\) 比当前子树根 \(x\) 大,而 \(x\) 的右子树为空,那就让 \(key\) 成为

    \(x\) 的右孩子。该操作的代价为:插入后,\(key\) 的深度。特别地,若树为空,则直接让新节点成为一个单个节点的树

    。(各节点关键码互不相等。对于“深度”的解释见末尾对 \(spaly\) 的描述)。
  2. 单旋最小值:将 \(spaly\) 中关键码最小的元素 \(xmin\) 单旋到根。操作代价为:单旋前 \(xmin\) 的深度。

    (对于单旋操作的解释见末尾对 \(spaly\) 的描述)。
  3. 单旋最大值:将 \(spaly\) 中关键码最大的元素 \(xmax\) 单旋到根。操作代价为:单旋前 \(xmax\) 的深度。
  4. 单旋删除最小值:先执行 2 号操作,然后把根删除。由于 2 号操作之后,根没有左子树,所以直接切断根和右子

    树的联系即可(具体见样例解释)。 操作代价同 2 号操 作。
  5. 单旋删除最大值:先执行 3 号操作,然后把根删除。 操作代价同 3 号操作。

对于不是 \(H\) 国的人,你可能需要了解一些 \(spaly\) 的知识,才能完成国王的任务:

\(a\).$ spaly$ 是一棵二叉树,满足对于任意一个节点 \(x\),它如果有左孩子 \(lx\),那么 \(lx\) 的关键码小于 \(x\) 的关键码。

如果有右孩子 \(rx\),那么 \(rx\) 的关键码大于 \(x\) 的关键码。

\(b\). 一个节点在 \(spaly\) 的深度定义为:从根节点到该节点的路径上一共有多少个节点(包括自己)。

\(c\). 单旋操作是对于一棵树上的节点 \(x\) 来说的。一开始,设 $ f$ 为 \(x\) 在树上的父亲。如果 \(x\) 为 \(f\) 的左孩子,那么

执行 \(zig(x)\) 操作(如上图中,左边的树经过 \(zig(x)\) 变为了右边的树),否则执行 \(zag(x)\) 操作(在上图中,将

右边的树经过 \(zag(f)\) 就变成了左边的树)。每当执 行一次 \(zig(x)\) 或者 \(zag(x)\),\(x\) 的深度减小 \(1\),如此反复,

直到 \(x\) 为根。总之,单旋 \(x\) 就是通过反复执行 \(zig\) 和 \(zag\) 将 \(x\) 变为根。

Input

第一行单独一个正整数 \(m\)。

接下来 \(m\) 行,每行描述一个操作:首先是一个操作编号 \(c \in [1,5]\),即问题描述中给出的五种操作中的编号,若 \(c
= 1\),则再输入一个非负整数 \(key\),表示新插入节点的关键码。

\(1 \leq m \leq 10^5,1 \leq key \leq 10^9\)

所有出现的关键码互不相同。任何一个非插入操作,一定保证树非空。在未执行任何操作之前,树为空

Output

输出共 \(m\) 行,每行一个整数,第 \(i\) 行对应第 \(i\) 个输入的操作的代价。

Sample Input

5

1 2

1 1

1 3

4

5

Sample Output

1

2

2

2

2


想法

首先发现,所有旋转上去的点要么最大要么最小,那么只会一直左旋或右旋

之后手玩几组发现,转上去后,有变化的只是它的子节点接到了它的父节点上,原本的根节点接到了它下面,其他的相对顺序都没变

那么这道题让我们维护的深度很好搞,就是它原本子节点的深度不变,它的深度变成1,其它点深度都+1

而对于删除根节点,深度也很好维护,就是所有点深度-1

接着考虑插入节点是怎么知道它应插到哪里。

继续画一画发现,我们可以维护每个节点直接相连有几个可以插新节点的位置(0或1或2),假设原本树上有 \(x\) 个点比我们要插的点小,那么新点就应插在第 \(x+1\) 个空位上。它的深度就是它父节点深度+1,我们在找空位的时候就能知道它的父节点是谁。

先把所有数据都进来,离散化一下。

线段树维护深度和空位数和某一个区间内在树中的节点数。

最大值/最小值怎么搞?可以用个 \(set\) 维护当前树中的点。

嗯,口胡地差不多了,感觉不太难。

写起来,啊啊啊啊啊怎么这么多细节啊啊啊啊啊!多多多多多多注意下。。。

挺好也挺毒瘤的一道题。


代码

啊,191行的代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<set> using namespace std; int read(){
int x=0;
char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
} const int N = 100005; int m,n;
struct ope{ int op,x; }a[N];
int b[N]; set<int> s;
int root,cnt,ch[N*2][2],sz[N*2],lazy[N*2],num[N*2];
int pos[N];
void build(int x,int l,int r){
sz[x]=lazy[x]=num[x]=0;
if(l==r) { pos[l]=x; return; }
int mid=(l+r)>>1;
build(ch[x][0]=++cnt,l,mid);
build(ch[x][1]=++cnt,mid+1,r);
}
void pushdown(int x){
if(lazy[x]==0) return ;
lazy[ch[x][0]]+=lazy[x];
lazy[ch[x][1]]+=lazy[x];
lazy[x]=0;
}
void addsz(int x,int l,int r,int c,int d){
sz[x]+=d;
if(l==r) return;
pushdown(x);
int mid=(l+r)>>1;
if(c<=mid) addsz(ch[x][0],l,mid,c,d);
else addsz(ch[x][1],mid+1,r,c,d);
}
void addnum(int x,int l,int r,int c,int d){
num[x]+=d;
if(l==r) return;
pushdown(x);
int mid=(l+r)>>1;
if(c<=mid) addnum(ch[x][0],l,mid,c,d);
else addnum(ch[x][1],mid+1,r,c,d);
}
void adddep(int x,int l,int r,int L,int R,int c){
if(L<=l && r<=R) { lazy[x]+=c; return; }
pushdown(x);
int mid=(l+r)>>1;
if(L<=mid) adddep(ch[x][0],l,mid,L,R,c);
if(R>mid) adddep(ch[x][1],mid+1,r,L,R,c);
}
void cgdep(int x,int l,int r,int c,int d){
if(l==r) { lazy[x]=d; return; }
pushdown(x);
int mid=(l+r)>>1;
if(c<=mid) cgdep(ch[x][0],l,mid,c,d);
else cgdep(ch[x][1],mid+1,r,c,d);
}
int find(int x,int l,int r,int c){
if(l==r) return l;
pushdown(x);
int mid=(l+r)>>1;
if(sz[ch[x][0]]>=c) return find(ch[x][0],l,mid,c);
return find(ch[x][1],mid+1,r,c-sz[ch[x][0]]);
}
int cal(int x,int l,int r,int c){
if(l==r) return num[x];
pushdown(x);
int mid=(l+r)>>1;
if(c<=mid) return cal(ch[x][0],l,mid,c);
return num[ch[x][0]]+cal(ch[x][1],mid+1,r,c);
}
int dep(int x,int l,int r,int c){
if(l==r) return lazy[x];
pushdown(x);
int mid=(l+r)>>1;
if(c<=mid) return dep(ch[x][0],l,mid,c);
return dep(ch[x][1],mid+1,r,c);
} int rt,fa[N],son[N][2]; void mkrt_min(int x){
if(x+1<=fa[x]-1) adddep(root,1,n,x+1,fa[x]-1,-1);
adddep(root,1,n,1,n,1);
cgdep(root,1,n,x,1);
son[fa[x]][0]=son[x][1];
if(son[x][1]) fa[son[x][1]]=fa[x];
else addsz(root,1,n,fa[x],1),addsz(root,1,n,x,-1);
fa[rt]=x; son[x][1]=rt; rt=x; fa[x]=0;
}
void mkrt_max(int x){
if(fa[x]+1<=x-1) adddep(root,1,n,fa[x]+1,x-1,-1);
adddep(root,1,n,1,n,1);
cgdep(root,1,n,x,1);
son[fa[x]][1]=son[x][0];
if(son[x][0]) fa[son[x][0]]=fa[x];
else addsz(root,1,n,fa[x],1),addsz(root,1,n,x,-1);
fa[rt]=x; son[x][0]=rt; rt=x; fa[x]=0;
} int main()
{
m=read();
for(int i=0;i<m;i++){
a[i].op=read();
a[i].x= (a[i].op==1) ? read() : 0 ;
if(a[i].x) b[++n]=a[i].x;
}
sort(b+1,b+1+n);
n=unique(b+1,b+1+n)-b-1; build(root=++cnt,1,n);
for(int i=0;i<m;i++){
if(a[i].op==1){
int x=lower_bound(b+1,b+1+n,a[i].x)-b;
if(s.size()==0){
s.insert(x);
rt=x;
addsz(root,1,n,x,2);
cgdep(root,1,n,x,1);
addnum(root,1,n,x,1);
printf("1\n");
}
else{
s.insert(x);
int y=find(root,1,n,cal(root,1,n,x)+1),d=dep(root,1,n,y)+1;
fa[x]=y;
son[fa[x]][x>fa[x]]=x;
addsz(root,1,n,fa[x],-1);
addsz(root,1,n,x,2);
cgdep(root,1,n,x,d);
addnum(root,1,n,x,1);
printf("%d\n",d);
}
}
else if(a[i].op==2){
int x=*s.begin();
if(rt==x) printf("1\n");
else{
printf("%d\n",dep(root,1,n,x));
mkrt_min(x);
}
}
else if(a[i].op==3){
int x=*(--s.end());
if(rt==x) printf("1\n");
else{
printf("%d\n",dep(root,1,n,x));
mkrt_max(x);
}
}
else if(a[i].op==4){
int x=*s.begin();
if(rt==x) printf("1\n");
else{
printf("%d\n",dep(root,1,n,x));
mkrt_min(x);
}
adddep(root,1,n,1,n,-1);
addsz(root,1,n,x,-sz[pos[x]]);
addnum(root,1,n,x,-1);
rt=son[x][1]; son[x][1]=0; fa[rt]=0;
s.erase(x);
}
else{
int x=*(--s.end());
if(rt==x) printf("1\n");
else{
printf("%d\n",dep(root,1,n,x));
mkrt_max(x);
}
adddep(root,1,n,1,n,-1);
addsz(root,1,n,x,-sz[pos[x]]); /**/
addnum(root,1,n,x,-1);
rt=son[x][0]; son[x][0]=0; fa[rt]=0;
s.erase(x);
}
} return 0;
}

[bzoj4825] [loj#2018] [Hnoi2017] 单旋的更多相关文章

  1. 并不对劲的bzoj4825:loj2018:p3721:[HNOI2017]单旋

    题目大意 spaly是一种数据结构,它是只有单旋的splay 有一个初始为空的spaly,\(m\)(\(m\leq10^5\))次操作,每个操作是以下5种中的一种: 1.向spaly中插入一个数(过 ...

  2. [BZOJ4825][HNOI2017]单旋(线段树+Splay)

    4825: [Hnoi2017]单旋 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 667  Solved: 342[Submit][Status][ ...

  3. 【BZOJ4825】[Hnoi2017]单旋 线段树+set

    [BZOJ4825][Hnoi2017]单旋 Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能 ...

  4. bzoj 4825: [Hnoi2017]单旋 [lct]

    4825: [Hnoi2017]单旋 题意:有趣的spaly hnoi2017刚出来我就去做,当时这题作死用了ett,调了5节课没做出来然后发现好像直接用lct就行了然后弃掉了... md用lct不知 ...

  5. 【LG3721】[HNOI2017]单旋

    [LG3721][HNOI2017]单旋 题面 洛谷 题解 20pts 直接模拟\(spaly\)的过程即可. 100pts 可以发现单旋最大.最小值到根,手玩是有显然规律的,发现只需要几次\(lin ...

  6. 4825: [Hnoi2017]单旋

    4825: [Hnoi2017]单旋 链接 分析: 以后采取更保险的方式写代码!!!81行本来以为不特判也可以,然后就总是比答案大1,甚至出现负数,调啊调啊调啊调~~~ 只会旋转最大值和最小值,以最小 ...

  7. bzoj4825 [Hnoi2017]单旋

    Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必 ...

  8. BZOJ4825: [Hnoi2017]单旋(Splay)

    题面 传送门 题解 调了好几个小时--指针太难写了-- 因为只单旋最值,我们以单旋\(\min\)为例,那么\(\min\)是没有左子树的,而它旋到根之后,它的深度变为\(1\),它的右子树里所有节点 ...

  9. 【bzoj4825】[Hnoi2017]单旋 线段树+STL-set

    题目描述 H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能.有一天 ...

随机推荐

  1. 【2016常州一中夏令营Day3】

    小 W 摆石子[问题描述]小 W 得到了一堆石子,要放在 N 条水平线与 M 条竖直线构成的网格的交点上.因为小 M 最喜欢矩形了,小 W 希望知道用 K 个石子最多能找到多少四边平行于坐标轴的长方形 ...

  2. 微软软件开发技术二十年回顾-.NET框架篇

    六. .NET框架篇 .NET是微软自从发布Windows 3.0以来最为激动人心的新技术,是微软战略上为下一个十年对服务器和桌面软件工程的第一步,是微软的一场世纪大豪赌.对于.NET,微软的定义是, ...

  3. 2018-2-13-wpf-GifBitmapDecoder-解析-gif-格式

    title author date CreateTime categories wpf GifBitmapDecoder 解析 gif 格式 lindexi 2018-2-13 17:23:3 +08 ...

  4. OpenCV与MFC实战之图像处理 样本采集小工具制作 c++MFC课程设计

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/12111102.html 入门不久的人可以通过opencv实战来锻炼一下学习opencv的成果, ...

  5. 使用 Visual Studio Code 进行远程开发

    使用 Visual Studio Code 进行远程开发 在完成了 AT 指令入门的学习之后,接下来就要使用 AT 指令进行 Socket 通信了.问题在于,之前 .NET 的 Socket 编程只需 ...

  6. 关于Qt中窗口的坐标

    主要是给自己以后参考,所以不会太仔细的讲解. #include "mainwindow.h" #include <QApplication> #include<Q ...

  7. css3新增属性有哪些?css3中常用的新增属性总结

    css3新增属性有哪些?来提问这个问题的人都应该知道css3是css的升级版本,那么,css3既然是升级版本,自然是会新增一些属性,接下来本篇文章将给大家介绍关于css3中常用的新增属性. 一.css ...

  8. 洛谷P-4782 2-sat+Tarjan

    https://www.luogu.org/problemnew/solution/P4782 这里的大佬已经说的够好了 #include<iostream> #include<cs ...

  9. 超详细!如何利用Huginn制作专属RSS

    前言 本文首发于个人网站,欢迎订阅.本篇博文接上利用Feed43为网站自制RSS源,上一篇讲解了RSS的简介以及利用Feed43自制专属RSS,Feed43有其优势,缺陷也很明显,不能高度自定义.有的 ...

  10. maven安装与常用命令

    maven安装: 下载地址http://maven.apache.org/download.cgi 1.安装好Java,配置好Java的环境变量(JDK) 2.下载apache-maven-3.5.2 ...