bzoj P4825 [Hnoi2017]单旋——solution
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 号操作。
![](http://www.lydsy.com/JudgeOnline/upload/201704/vv1.jpg)
对于不是 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
1 2
1 1
1 3
4
5
Sample Output
1
2
2
2
2
2
2
2
2
——by bzoj
http://www.lydsy.com/JudgeOnline/problem.php?id=4825
这个bzoj的蓝底好赞啊
如果是双旋splay,就是模板题;
然而是单旋splay,
直接模拟?
亲测洛谷t成暴力分(废话,本来就是暴力)
可能出题人刻意卡了,也可能本来就非常好卡
考虑尝试其他方法;
-操作1 k: 找到k的前驱,后继,如果当前树上前驱无右子节点,则k代表的点挂在前驱右子节点的位置,否则挂在后继的左子节点处
可以证明前驱的右子节点与后继的左子节点至少有一个不存在;
(因为在k加入集合之前,集合中不存在大小在k的前驱和后继间的元素,于是若k的前驱有右子树,则k的后继必在此子树上)
(否则这棵子树上的所有点都在k的前驱后继之间)
(既然k的后继在k的前驱的右子树上,那么他的左子树上所有元素都在k的前驱后继之间,于是他没有左子节点)
-操作2和3: 观察单旋最小值或最大值的过程,发现此过程对树的结构影响极少,
只是把这个代表最大|小值的节点剥离树,
他的子节点接替他做父节点的子节点(他只有一个子节点),
然后把剩余整个树挂在他的左|右子树位置,
-操作4和5: 相当于操作2和3省去最后一个过程,然后保留剩下的树,而把剥离的点弃置
维护树的结构的部分可以直接用LCT;
查找加入节点的位置——即找前驱后继可以直接用MAP实现
代码:
(又一次证明了我的代码能力和STL能力有多烂)
(LCT的写法有点奇怪……不支持换根的写法)
1 #include<map> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 map<int ,int > MAP; 7 int m,top; 8 struct DT{ 9 int ch[2],CH[2],fa,FA; 10 int size,num; 11 }; 12 struct Splay{ 13 DT data[100010]; 14 void splay(int x){ 15 int fa,fafa; 16 for(fa=data[x].fa;che(x);roll(x),fa=data[x].fa){ 17 fafa=data[fa].fa; 18 if(che(fa)){ 19 if((data[fa].ch[1]==x)^(data[fafa].ch[1]==fa)) 20 roll(x); 21 else 22 roll(fa); 23 } 24 } 25 } 26 void roll(int x){ 27 int fa=data[x].fa,fafa=data[fa].fa,wh=data[fa].ch[1]==x; 28 data[fa].ch[wh]=data[x].ch[wh^1],data[data[fa].ch[wh]].fa=fa; 29 data[fa].fa=x,data[x].ch[wh^1]=fa; 30 data[x].fa=fafa; 31 if(data[fafa].ch[0]==fa||data[fafa].ch[1]==fa) 32 data[fafa].ch[data[fafa].ch[1]==fa]=x; 33 up(fa),up(x); 34 } 35 bool che(int x){ 36 return data[data[x].fa].ch[0]==x||data[data[x].fa].ch[1]==x; 37 } 38 void up(int x){ 39 data[x].size=data[data[x].ch[0]].size+data[data[x].ch[1]].size+1; 40 } 41 }; 42 struct LCT{ 43 Splay T; 44 int root; 45 void Mak_data(int id,int num){ 46 T.data[id].ch[0]=T.data[id].ch[1]=T.data[id].CH[0]=T.data[id].CH[1]=0; 47 T.data[id].FA=T.data[id].fa=0; 48 T.data[id].size=1,T.data[id].num=num; 49 } 50 void Link(int fa,int son,int wh){ 51 Access(son),T.splay(son); 52 T.data[son].FA=fa; 53 T.data[son].fa=fa; 54 T.data[fa].CH[wh]=son; 55 Access(son); 56 } 57 void Cut(int fa,int son){ 58 Access(fa),T.splay(son); 59 T.data[son].FA=T.data[son].fa=0; 60 T.data[fa].CH[T.data[fa].CH[1]==son]=0; 61 } 62 void Access(int x){ 63 int y=0; 64 while(x){ 65 T.splay(x); 66 T.data[x].ch[1]=y,T.up(x); 67 y=x,x=T.data[x].fa; 68 } 69 } 70 }; 71 LCT lct; 72 inline void in(int &ans) 73 { 74 ans=0;bool p=false;char ch=getchar(); 75 while((ch>'9' || ch<'0')&&ch!='-') ch=getchar(); 76 if(ch=='-') p=true,ch=getchar(); 77 while(ch<='9'&&ch>='0') ans=ans*10+ch-'0',ch=getchar(); 78 if(p) ans=-ans; 79 } 80 int main() 81 { 82 int i,j,k,templ,tempr,temp,f,c; 83 map<int ,int >::iterator iter; 84 in(m); 85 lct.root=0; 86 for(i=1;i<=m;i++){ 87 in(j); 88 if(j==1){ 89 in(k); 90 lct.Mak_data(++top,k); 91 if(lct.root){ 92 iter=MAP.upper_bound(k); 93 tempr=iter->second,templ=0; 94 if(iter!=MAP.begin())iter--,templ=iter->second; 95 if(templ&&!lct.T.data[templ].CH[1]) 96 lct.Link(templ,top,1); 97 else 98 lct.Link(tempr,top,0); 99 } 100 else 101 lct.root=top; 102 lct.Access(top),lct.T.splay(top); 103 printf("%d\n",lct.T.data[top].size); 104 MAP[k]=top; 105 } 106 else{ 107 iter=MAP.end(),iter--; 108 if(j&1) 109 temp=iter->second; 110 else 111 temp=MAP.begin()->second; 112 lct.Access(temp),lct.T.splay(temp); 113 printf("%d\n",lct.T.data[temp].size); 114 if(lct.T.data[temp].FA&&lct.T.data[temp].CH[!(j&1)]){ 115 f=lct.T.data[temp].FA,c=lct.T.data[temp].CH[!(j&1)]; 116 lct.Cut(temp,c),lct.Cut(f,temp); 117 lct.Link(f,c,j&1); 118 } 119 else{ 120 if(lct.T.data[temp].FA||lct.T.data[temp].CH[!(j&1)]) 121 if(lct.T.data[temp].FA) 122 lct.Cut(lct.T.data[temp].FA,temp); 123 else 124 lct.root=lct.T.data[temp].CH[!(j&1)],lct.Cut(temp,lct.T.data[temp].CH[!(j&1)]); 125 else{ 126 if(j>3) 127 lct.root=0,MAP.erase(lct.T.data[temp].num); 128 continue; 129 } 130 } 131 if(j<4) 132 lct.Link(temp,lct.root,!(j&1)),lct.root=temp; 133 else 134 MAP.erase(lct.T.data[temp].num); 135 } 136 } 137 }
Just close your eyes, you`ll be alright, no one can hurt you after you die.