[AH/HNOI2017]单旋
Posted Z-Y-Y-S
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[AH/HNOI2017]单旋相关的知识,希望对你有一定的参考价值。
题目描述
H国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构。伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了H国的必修技能。有一天,邪恶的“卡”带着他的邪恶的“常数”来企图毁灭H国。“卡”给H国的人洗脑说,splay如果写成单旋的,将会更快。“卡”称“单旋splay”为“spaly”。虽说他说的很没道理,但还是有H国的人相信了,小H就是其中之一,spaly马上成为他的信仰。而H国的国王,自然不允许这样的风气蔓延,国王构造了一组数据,数据由m(不超过10^5)个操作构成,他知道这样的数据肯定打垮spaly,但是国王还有很多很多其他的事情要做,所以统计每个操作
所需要的实际代价的任务就交给你啦。数据中的操作分为5种:
-
插入操作:向当前非空spaly中插入一个关键码为key的新孤立节点。插入方法为,先让key和根比较,如果key比根小,则往左子树走,否则往右子树走,如此反复,直到某个时刻,key比当前子树根x小,而x的左子树为空,那就让key成为x的左孩子;或者key比当前子树根x大,而x的右子树为空,那就让key成为x的右孩子。该操作的代价为:插入后,key的深度。特别地,若树为空,则直接让新节点成为一个单个节点的树。(各节点关键码互不相等。对于“深度”的解释见末尾对spaly的描述。)
-
单旋最小值:将spaly中关键码最小的元素xmin单旋到根。操作代价为:单旋前xmin的深度。(对于单旋操作的解释见末尾对spaly的描述。)
-
单旋最大值:将spaly中关键码最大的元素xmax单旋到根。操作代价为:单旋前xmax的深度。
-
单旋删除最小值:先执行2号操作,然后把根删除。由于2号操作之后,根没有左子树,所以直接切断根和右子树的联系即可。(具体见样例解释)。操作代价同2号操作。
- 单旋删除最大值:先执行3号操作,然后把根删除。操作代价同3号操作。
对于不是H国的人,你可能需要了解一些spaly的知识,才能完成国王的任务:
-
spaly是一棵二叉树,满足对于任意一个节点x,它如果有左孩子lx,那么lx的关键码小于x的关键码。如果有右孩子rx,那么rx的关键码大于x的关键码。
-
一个节点在spaly的深度定义为:从根节点到该节点的路径上一共有多少个节点(包括自己)。
- 单旋操作是对于一棵树上的节点x来说的。一开始,设f为x在树上的父亲。如果x为f的左孩子,那么执行zig(x)操作(如上图中,左边的树经过zig(x)变为了右边的树),否则执行zag(x)操作(在上图中,将右边的树经过zag(f)就变成了左边的树)。每当执行一次zig(x)或者zag(x),x的深度减小1,如此反复,直到x为根。总之,单旋x就是通过反复执行zig和zag将x变为根。
输入输出格式
输入格式:
输入文件名为 splay.in。
第一行单独一个正整数 m (1 <= m <= 10^5)。
接下来 m 行,每行描述一个操作:首先是一个操作编号 c( 1<=c<=5),既问题描述中给出的 5 种操作中的编号,若 c= 1,则再输入一个非负整数 key,表示新插入节点的关键码。
输出格式:
输出文件名为 splay.out。
输出共 m 行,每行一个整数,第 i 行对应第 i 个输入的操作的代价。
输入输出样例
5 1 2 1 1 1 3 4 5
1 2 2 2 2
说明
20%的数据满足: 1 <= m <= 1000。
另外 30%的数据满足: 不存在 4,5 操作。
100%的数据满足: 1<=m<=10^5; 1<=key<=10^9。 所有出现的关键码互不相同。 任何一个非插入操作,一定保证树非空。 在未执行任何操作之前,树为空。
这道题的关键之处在于:除了插入以外,只操作最大/最小值
所以每次旋转只有一个固定方向,对树的影响其实不大。
以旋转最小值为例,除了最小值的右子树,其他节点深度+1,如果删去的话
因为它作为根节点,没有左子树,所以直接删去,所有点深度-1。如果不删,最小点深度为1
对与插入,节点的深度是当前spaly中比它小中最大的、比它大的中最小的,两个节点深度更大值+1
用线段树维护深度,线段树中,每个叶节点表示第i大的值得深度信息,显然这需要离线
cnt[rt]表示区间内有多少个点加入了树
mark[rt]区间加的延迟标记
mi[rt]当前区间最小的加入了树的节点的深度
mx[rt]当前区间最大的加入了树的节点的深度
t[rt]表示该区间存在的最小深度,未加入树的空节点不算
这样我们维护这个信息
对于mi,只要存在左节点就赋值,否则就赋值右节点,mx同理
查找小于插入值的最大节点的话,当线段树的右端点为插入节点的大小时
mx[rt]显然就是小于插入值的最大节点的深度(大于的最小值同理,用mi就行了)
对于找到最小值(深度为d)的右子树,用一个find函数,找到q为最小值的父亲
这样找到的右子树肯定是一个连续区间,所以(其他节点深度+1)=>([q,n]+1)
如何找到,如果左节点的最小深度大于d或为空,显然找右节点,否则找左节点
这样可以找到深度刚好小于最小值且大于最小值的节点q
似乎和正常的splay一样,还要加入最大值和最小值,也就是线段树中0和n+1
这题代码略长,有3.6KB,调了一天(都怪FGO出活动)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 using namespace std; 7 struct Ask 8 { 9 int x,id; 10 }A[1000001]; 11 const int N=100005,M=400005; 12 int m,n,a[N],opt[N],now,mi[M],mx[M],cnt[M],t[M],mark[M],dep; 13 bool cmp(Ask a,Ask b) 14 { 15 return a.x<b.x; 16 } 17 int gi() 18 { 19 char ch=getchar(); 20 int x=0; 21 while (ch<‘0‘||ch>‘9‘) ch=getchar(); 22 while (ch>=‘0‘&&ch<=‘9‘) 23 { 24 x=x*10+ch-‘0‘; 25 ch=getchar(); 26 } 27 return x; 28 } 29 void pushdown(int rt) 30 { 31 if (mark[rt]) 32 { 33 if (cnt[rt*2]) 34 { 35 t[rt*2]+=mark[rt]; 36 mi[rt*2]+=mark[rt]; 37 mx[rt*2]+=mark[rt]; 38 mark[rt*2]+=mark[rt]; 39 } 40 if (cnt[rt*2+1]) 41 { 42 t[rt*2+1]+=mark[rt]; 43 mi[rt*2+1]+=mark[rt]; 44 mx[rt*2+1]+=mark[rt]; 45 mark[rt*2+1]+=mark[rt]; 46 } 47 mark[rt]=0; 48 } 49 } 50 void pushup(int rt) 51 { 52 if (cnt[rt*2+1]) mx[rt]=mx[rt*2+1]; 53 else mx[rt]=mx[rt*2]; 54 if (cnt[rt*2]) mi[rt]=mi[rt*2]; 55 else mi[rt]=mi[rt*2+1]; 56 t[rt]=min(t[rt*2],t[rt*2+1]); 57 cnt[rt]=cnt[rt*2]+cnt[rt*2+1]; 58 } 59 int get1(int rt,int l,int r,int v) 60 { 61 if (v==r) 62 { 63 return mx[rt]; 64 } 65 pushdown(rt); 66 int mid=(l+r)/2; 67 if (v<=mid) return get1(rt*2,l,mid,v); 68 int x=get1(rt*2+1,mid+1,r,v); 69 if (x>0) return x; 70 return mx[rt*2]; 71 } 72 int get2(int rt,int l,int r,int v) 73 { 74 if (v==l) 75 { 76 return mi[rt]; 77 } 78 pushdown(rt); 79 int mid=(l+r)/2; 80 if (v>mid) return get2(rt*2+1,mid+1,r,v); 81 int x=get2(rt*2,l,mid,v); 82 if (x>0) return x; 83 return mi[rt*2+1]; 84 } 85 void insert(int rt,int l,int r,int d,int v) 86 { 87 if (l==r) 88 { 89 t[rt]=mi[rt]=mx[rt]=d; 90 cnt[rt]=1; 91 return; 92 } 93 pushdown(rt); 94 int mid=(l+r)/2; 95 if (v<=mid) insert(rt*2,l,mid,d,v); 96 else insert(rt*2+1,mid+1,r,d,v); 97 pushup(rt); 98 } 99 int getmin(int rt,int l,int r) 100 { 101 if (l==r) 102 { 103 dep=t[rt]; 104 return l; 105 } 106 pushdown(rt); 107 int mid=(l+r)/2; 108 if (cnt[rt*2]>0) return getmin(rt*2,l,mid); 109 else return getmin(rt*2+1,mid+1,r); 110 } 111 int getmax(int rt,int l,int r) 112 { 113 if (l==r) 114 { 115 dep=t[rt]; 116 return l; 117 } 118 pushdown(rt); 119 int mid=(l+r)/2; 120 if (cnt[rt*2+1]>0) return getmax(rt*2+1,mid+1,r); 121 else return getmax(rt*2,l,mid); 122 } 123 int find1(int rt,int l,int r,int d) 124 { 125 if (l==r) 126 return l; 127 pushdown(rt); 128 int mid=(l+r)/2; 129 if (cnt[rt*2]==0||t[rt*2]>d) return find1(rt*2+1,mid+1,r,d); 130 else return find1(rt*2,l,mid,d); 131 } 132 int find2(int rt,int l,int r,int d) 133 { 134 if (l==r) 135 return l; 136 pushdown(rt); 137 int mid=(l+r)/2; 138 if (cnt[rt*2+1]==0||t[rt*2+1]>d) return find2(rt*2,l,mid,d); 139 else return find2(rt*2+1,mid+1,r,d); 140 } 141 void change(int rt,int l,int r,int L,int R,int x) 142 { 143 if (!cnt[rt]) return; 144 if (l>=L&&r<=R) 145 { 146 mark[rt]+=x; 147 t[rt]+=x; 148 mx[rt]+=x;mi[rt]+=x; 149 return; 150 } 151 pushdown(rt); 152 int mid=(l+r)/2; 153 if (L<=mid) change(rt*2,l,mid,L,R,x); 154 if (R>mid) change(rt*2+1,mid+1,r,L,R,x); 155 pushup(rt); 156 } 157 void Delet(int rt,int l,int r,int v) 158 { 159 if (l==r) 160 { 161 t[rt]=n; 162 mx[rt]=mi[rt]=cnt[rt]=0; 163 return; 164 } 165 pushdown(rt); 166 int mid=(l+r)/2; 167 if (v<=mid) Delet(rt*2,l,mid,v); 168 else Delet(rt*2+1,mid+1,r,v); 169 pushup(rt); 170 } 171 int main() 172 {int i; 173 cin>>m; 174 for (i=1;i<=m;i++) 175 { 176 opt[i]=gi(); 177 if (opt[i]==1) 178 { 179 A[++n].x=gi(); 180 A[n].id=n; 181 } 182 } 183 sort(A+1,A+n+1,cmp); 184 for (i=1;i<=n;i++) a[A[i].id]=i; 185 n++; 186 now=0; 187 memset(t,127,sizeof(t)); 188 for (i=1;i<=m;i++) 189 { 190 if (opt[i]==1) 191 { 192 now++; 193 insert(1,0,n,dep=max(get1(1,0,n,a[now]),get2(1,0,n,a[now]))+1,a[now]); 194 } 195 else if (opt[i]==2||opt[i]==4) 196 { 197 int p=getmin(1,0,n),q; 198 Delet(1,0,n,p); 199 q=find1(1,0,n,dep); 200 change(1,0,n,q,n,1); 201 if (opt[i]==4) 202 change(1,0,n,0,n,-1); 203 else insert(1,0,n,1,p); 204 } 205 else if (opt[i]==3||opt[i]==5) 206 { 207 int p=getmax(1,0,n),q; 208 Delet(1,0,n,p); 209 q=find2(1,0,n,dep); 210 change(1,0,n,0,q,1); 211 if (opt[i]==5) 212 change(1,0,n,0,n,-1); 213 else insert(1,0,n,1,p); 214 } 215 printf("%d\n",dep); 216 } 217 }
以上是关于[AH/HNOI2017]单旋的主要内容,如果未能解决你的问题,请参考以下文章