4825: [Hnoi2017]单旋

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 667  Solved: 342
[Submit][Status][Discuss]

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∈[1,5],即问题描述中给出的五种操作中的编号,若 c
 = 1,则再输入一个非负整数 key,表示新插入节点的关键码。
1≤m≤10^5,1≤key≤10^9
所有出现的关键码互不相同。任何一个非插入操作,一定保证树非空。在未执行任何操作之前,树为空

Output

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

Sample Input

5
1 2
1 1
1 3
4
5

Sample Output

1
2
2
2
2

HINT

 

Source

代码用时:2h

感觉代码难度很低啊为什么我还是花了很长时间。。

思维比较简单,只要把三种操作想清楚了就差不多了。

比较自然的想法应该是Splay,但线段树实现复杂度和常数都更优。

线段树做法网上好像只找到一个(而且没有标记永久化的代码并不是很优美常数也稍大)

这里有一个自认为比较优的做法。

(本题线段树做法的思想还是有点巧妙的,利用了题目只单旋最值的设定)

12140KB,1108ms

 #include<set>
#include<cstdio>
#include<algorithm>
#include<iostream>
#define rep(i,l,r) for (int i=l; i<=r; i++)
using namespace std; template<typename T>inline void rd(T &x){
int t; char ch;
for (t=; !isdigit(ch=getchar()); t=(ch=='-'));
for (x=ch-''; isdigit(ch=getchar()); x=x*+ch-'');
if (t) x=-x;
} const int N=;
int m,tot,d,rt,D[N<<],rs[N],ls[N],fa[N],a[N],b[N],op[N][];
set<int>S;
set<int>::iterator it,sub,pro; void mdf(int x,int L,int R,int l,int r,int k){
if (L==l && R==r){ D[x]+=k; return; }
int mid=(L+R)>>;
if (r<=mid) mdf(x<<,L,mid,l,r,k);
else if (l>mid) mdf((x<<)|,mid+,R,l,r,k);
else mdf(x<<,L,mid,l,mid,k),mdf((x<<)|,mid+,R,mid+,r,k);
} int que(int x,int L,int R,int k){
if (L==R) return D[x];
int mid=(L+R)>>;
if (k<=mid) return D[x]+que(x<<,L,mid,k);
else return D[x]+que((x<<)|,mid+,R,k);
} int main(){
freopen("bzoj4825.in","r",stdin);
freopen("bzoj4825.out","w",stdout);
rd(m);
rep(i,,m){
rd(op[i][]); if (op[i][]==) rd(op[i][]);
if (op[i][]==) b[++tot]=op[i][];
}
sort(b+,b+tot+); tot=unique(b+,b+tot+)-b-;
rep(i,,m) if (op[i][]==) a[i]=lower_bound(b+,b+tot+,op[i][])-b;
rep(i,,m){
if (op[i][]==){
if (S.empty()) rt=a[i],d=;
else{
it=S.upper_bound(a[i]),sub=it;
if (sub==S.begin()) ls[*sub]=a[i],fa[a[i]]=*sub,d=que(,,tot,*sub)+;
else{
pro=--it;
if (sub==S.end()) rs[*pro]=a[i],fa[a[i]]=*pro,d=que(,,tot,*pro)+;
else if (que(,,tot,*sub)<que(,,tot,*pro))
rs[*pro]=a[i],fa[a[i]]=*pro,d=que(,,tot,*pro)+;
else ls[*sub]=a[i],fa[a[i]]=*sub,d=que(,,tot,*sub)+;
}
}
printf("%d\n",d); mdf(,,tot,a[i],a[i],d-que(,,tot,a[i]));
S.insert(a[i]);
}
if (op[i][]== || op[i][]==){
it=S.begin(); d=que(,,tot,*it); printf("%d\n",d);
if ((*it)!=rt){
mdf(,,tot,,tot,); mdf(,,tot,*it,*it,-d);
int t=fa[*it]; ls[t]=fa[*it]=;
if (rs[*it]){
mdf(,,tot,(*it)+,t-,-);
fa[rs[*it]]=t; ls[t]=rs[*it];
}
rs[*it]=rt; fa[rt]=*it; rt=*it;
}
if (op[i][]==) rt=rs[*it],rs[*it]=fa[rt]=,mdf(,,tot,,tot,-),S.erase(*it);
}
if (op[i][]== || op[i][]==){
it=S.end(); it--; d=que(,,tot,*it); printf("%d\n",d);
if ((*it)!=rt){
mdf(,,tot,,tot,); mdf(,,tot,*it,*it,-d);
int t=fa[*it]; rs[t]=fa[*it]=;
if (ls[*it]){
mdf(,,tot,t+,(*it)-,-);
fa[ls[*it]]=t; rs[t]=ls[*it];
}
ls[*it]=rt; fa[rt]=*it; rt=*it;
}
if (op[i][]==) rt=ls[*it],ls[*it]=fa[rt]=,mdf(,,tot,,tot,-),S.erase(*it);
}
}
return ;
}

惊了,splay跑的比线段树还快。

5752KB,900ms

代码用时:1h

一开始想自己写出来,思路并不复杂但代码很繁琐(模板占了大部分但想敲对并不容易)。

看了网上的代码抄了一份下来竟然1A,可能是我抄代码的经历中最顺利的一次吧。

但抄来的代码终究不是自己的,要勇于写这种代码稍长的题目,后面还会有比这长的多的题!

 #include<cstdio>
#include<iostream>
#include<algorithm>
#define ls c[x][0]
#define rs c[x][1]
#define rep(i,l,r) for (int i=l; i<=r; i++)
using namespace std; template<typename T>inline void rd(T &x){
int t; char ch;
for (t=; !isdigit(ch=getchar()); t=(ch=='-'));
for (x=ch-''; isdigit(ch=getchar()); x=x*+ch-'');
if (t) x=-x;
} const int inf=,N=;
int n,cnt,rt,Q,fa[N],v[N],sz[N],c[N][],s[N],dep[N],mn[N]; void push(int x){
v[ls]+=v[x]; dep[ls]+=v[x]; mn[ls]+=v[x];
v[rs]+=v[x]; dep[rs]+=v[x]; mn[rs]+=v[x];
v[x]=;
} void upd(int x){
sz[x]=sz[ls]+sz[rs]+; mn[x]=dep[x];
if (ls) mn[x]=min(mn[x],mn[ls]);
if (rs) mn[x]=min(mn[x],mn[rs]);
} void rot(int &rt,int x){
int y=fa[x],z=fa[y],w=(c[y][]==x);
if (y==rt) rt=x; else c[z][c[z][]==y]=x;
fa[x]=z; fa[y]=x; fa[c[x][w^]]=y;
c[y][w]=c[x][w^]; c[x][w^]=y; upd(y);
} void splay(int &rt,int x){
while (x!=rt) {
int y=fa[x],z=fa[y];
if (y!=rt){
if ((c[z][]==y)^(c[y][]==x)) rot(rt,x); else rot(rt,y);
}
rot(rt,x);
}
upd(x);
} void ins(int &x,int S,int d,int lst){
if (!x){
x=++cnt, s[cnt]=S; dep[cnt]=mn[cnt]=d;
sz[cnt]=; fa[cnt]=lst; return;
}
ins(c[x][S>s[x]],S,d,x); upd(x);
} int getpre(int x,int S){
if (!x) return ;
if (v[x]) push(x);
if (s[x]>S) return getpre(c[x][],S);
Q=getpre(c[x][],S);
if (Q) return Q; else return x;
} int getnxt(int x,int S){
if (!x) return ;
if (v[x]) push(x);
if (s[x]<S) return getnxt(rs,S);
Q=getnxt(ls,S);
if (Q) return Q; else return x;
} int find(int x,int k){
if (v[x]) push(x);
if (sz[ls]+==k) return x;
if (sz[ls]+<k) return find(rs,k-sz[ls]-);
return find(ls,k);
} int getl(int x,int d){
if (!x) return ;
if (v[x]) push(x);
if (min(mn[ls],dep[x])>=d) return getl(rs,d)+sz[ls]+;
else return getl(ls,d);
} int getr(int x,int d){
if (!x) return ;
if (v[x]) push(x);
if (min(mn[rs],dep[x])>=d) return getr(ls,d)+sz[rs]+;
else return getr(rs,d);
} int split(int l,int r){
int t1=find(rt,l-),t2=find(rt,r+);
splay(rt,t1); splay(c[rt][],t2); return c[c[rt][]][];
} void mdf(int l,int r,int ad){
int y=split(l,r); v[y]+=ad; mn[y]+=ad; dep[y]+=ad;
} void change(int x,int S){
if (v[x]) push(x);
if (s[x]==S) dep[x]=; else change(c[x][S>s[x]],S);
upd(x);
} int main(){
freopen("bzoj4825.in","r",stdin);
freopen("bzoj4825.out","w",stdout);
rd(n); ins(rt,-inf,inf,); ins(rt,inf,inf,); mn[]=inf;
rep(i,,n){
int op,x; rd(op);
if (op==){
rd(x); int t1=getpre(rt,x),t2=getnxt(rt,x);
int D=max(t1> ? dep[t1] : ,t2> ? dep[t2] : )+;
ins(rt,x,D,); splay(rt,cnt); printf("%d\n",D);
}
if (!(op&)){
int x=find(rt,),y=min(getl(rt,dep[x]),sz[rt]-)-;
printf("%d\n",dep[x]); mdf(,sz[rt]-,);
if (y>) mdf(,y+,-);
change(rt,s[x]);
}
if ((op&)&&(op>)){
int x=find(rt,sz[rt]-),y=min(getr(rt,dep[x]),sz[rt]-)-;
printf("%d\n",dep[x]); mdf(,sz[rt]-,);
if (y>) mdf(sz[rt]-y,sz[rt]-,-);
change(rt,s[x]);
}
if (op>=){
if (op==) splay(rt,find(rt,));
else splay(rt,find(rt,sz[rt]-));
int l=(op==),r=l^,y=c[rt][l];
c[y][r]=c[rt][r]; fa[y]=; fa[c[rt][r]]=y; rt=y;
v[rt]-=; upd(rt);
}
}
return ;
}

[BZOJ4825][HNOI2017]单旋(线段树+Splay)的更多相关文章

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

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

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

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

  3. BZOJ.4825.[AHOI/HNOI2017]单旋(线段树)

    BZOJ LOJ 洛谷 这题不难啊,我怎么就那么傻,拿随便一个节点去模拟.. 我们只需要能够维护,将最小值或最大值转到根.模拟一下发现,对于最小值,它的右子树深度不变(如果存在),其余节点深度全部\( ...

  4. 洛谷P3721 [AH2017/HNOI2017]单旋(线段树 set spaly)

    题意 题目链接 Sol 这题好毒瘤啊.. 首先要观察到几个性质: 将最小值旋转到根相当于把右子树变为祖先的左子树,然后将原来的根变为当前最小值 上述操作对深度的影响相当于右子树不变,其他的位置-1 然 ...

  5. BZOJ4825 [Hnoi2017]单旋 【线段树】

    题目链接 BZOJ4825 题解 手模一下操作,会发现一些很优美的性质: 每次旋到根,只有其子树深度不变,剩余点深度\(+1\) 每次旋到根,[最小值为例]右儿子接到其父亲的左儿子,其余点形态不改变, ...

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

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

  7. bzoj4825 [Hnoi2017]单旋

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

  8. [BZOJ4825][HNOI2017]单旋spaly

    BZOJ Luogu 题目太长了,就不放了. 题解 首先声明一点,无论是splay还是spaly,插入一个新的元素,都要rotate到根!所以说题目也算是给了一个错误示范吧. 我们发现把最值旋转到根并 ...

  9. 4825: [Hnoi2017]单旋

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

随机推荐

  1. Java面试中常问的Spring方面问题(涵盖七大方向共55道题,含答案)

    1.一般问题 1.1. 不同版本的 Spring Framework 有哪些主要功能? VersionFeatureSpring 2.5发布于 2007 年.这是第一个支持注解的版本.Spring 3 ...

  2. E - Is It A Tree? 并查集判断是否为树

    题目链接:https://vjudge.net/contest/271361#problem/E 具体思路:运用并查集,每一次连接上一个点,更新他的父亲节点,如果父亲节点相同,则构不成树,因为入读是2 ...

  3. Vue-Module

    由于使用单一状态树,应用的所有状态会集中到一个比较大的对象.当应用变得非常复杂时,store 对象就有可能变得相当臃肿. 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) ...

  4. python常用运维脚本实例【转】

    file是一个类,使用file('file_name', 'r+')这种方式打开文件,返回一个file对象,以写模式打开文件不存在则会被创建.但是更推荐使用内置函数open()来打开一个文件 . 首先 ...

  5. 174.Dungeon Game---dp

    题目链接 题目大意:从左上角到右下角,每一个格子都有各自的权值,如果权值为负,则当到达时,要失血:如果权值为正,则当到达时,要加血.当到达某个格子时,当前血量<=0,则死亡,到达不了右下角,所以 ...

  6. hdu 4347 The Closest M Points (kd树)

    版权声明:本文为博主原创文章,未经博主允许不得转载. hdu 4347 题意: 求k维空间中离所给点最近的m个点,并按顺序输出  . 解法: kd树模板题 . 不懂kd树的可以先看看这个 . 不多说, ...

  7. 《深入理解Java虚拟机》笔记--第三章 、垃圾收集器与内存分配策略

    1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言. Java的垃圾收集(Garbage Collection)主要关注堆和方法区的内存回收. 在GC堆进行回收前,第一件 ...

  8. window7 开启自带 ftp

    添加 ftp 用户 在windows里添加一个用户.这个其实是你ftp的用户.当然你可以使用匿名访问,但是这样不怎么安全,要知道ftp外网其实也是可以连进来的.去把密码设一下,标准用户就可以了,不用管 ...

  9. 【转载】python import和from import

    import和from import都是将其他模块导入当前模块中. 刚开始一直以为import和from import唯一的区别,就是from import可以少写一些模块名.虽然from XX im ...

  10. git配置用户名跟邮箱

    因为我有两个git账号 所以我现在要改变我的默认用户名跟邮件 我就需要去终端设置用户名跟邮箱 具体的命令行就是 设置git的用户名 git config --global user.name &quo ...