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. 【21.58%】【codeforces 746D】Green and Black Tea

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  2. dotnet 如何调试某个文件是哪个代码创建

    我发现了自己的软件,会在桌面创建一个 1.txt 文件,但是我不知道是哪个代码创建的,那么如何进行快速的调试找到是哪个代码创建的 最简单的方法是使用 VisualStudio 全局搜 1.txt 看是 ...

  3. 微软软件开发技术二十年回顾-MFC篇

    三. MFC篇 Windows API是面向过程的接口,因此对于当时的编程技术来说,它是完美无缺的.但是,随着人们逐渐使用C++进行Windows程序的开发,迫切需要建立与Windows API的面向 ...

  4. 【53.57%】【codeforces 610C】Harmony Analysis

    time limit per test3 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...

  5. js解决跨域下载文件

    之前用的是a标签的方式,同源是没有问题的,但一跨域就不行了,试了其它方法,不是报跨域错误,就是在当前页面打开文件,体验相当不好. data = data.replace(/\\/g, '/'); va ...

  6. Cmder安装与使用

    越来越多人使用Cmder代替Windows的cmd(毕竟其界面太Lower了),但是每次用Cmder都要回到安装目录查找之后才能使用,真的很麻烦,有木有可以像Git一样右键就是可以用的方法呢?答案当然 ...

  7. 看demo1

    http://pytorch-cn.readthedocs.io/zh/latest/package_references/torch/ pytorch文档 1.json JSON(JavaScrip ...

  8. 保存会话数据的两种技术,Cookie,Session

    CookieCookie是客户端技术,服务器把每个用户的数据以cookie的形式写给用户各自的浏览器.当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去.这样,web资源处理的就是用 ...

  9. C# 初识接口 Interface

    什么是接口? 接口(interface)用来定义一种程序的协定.实现接口的类或者结构要与接口的定义严格一致.有了这个协定,就可以抛开编程语言的限制(理论上).C#接口可以从多个基接口继承,而类或结构可 ...

  10. $CF949D\ Curfew$ 二分/贪心

    正解:二分/贪心 解题报告: 传送门$QwQ$ 首先这里是二分还是蛮显然的?考虑二分那个最大值,然后先保证一个老师是合法的再看另一个老师那里是否合法就成$QwQ$. 发现不太会搞这个合不合法的所以咕了 ...