郁闷的出纳员 HYSBZ - 1503
OIER公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的
工资。这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好
,就可能把每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把他们的工资扣除一个相同的量。我
真不知道除了调工资他还做什么其它事情。工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位
员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。每位员
工的工资下界都是统一规定的。每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘
了一位新员工,我就得为他新建一个工资档案。老板经常到我这边来询问工资情况,他并不问具体某位员工的工资
情况,而是问现在工资第k多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后
告诉他答案。好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样
,不是很困难吧?
Input
第一行有两个非负整数n和min。n表示下面有多少条命令,min表示工资下界。
接下来的n行,每行表示一条命令。命令可以是以下四种之一:
名称 格式 作用
I命令 I_k 新建一个工资档案,初始工资为k。
如果某员工的初始工资低于工资下界,他将立刻离开公司。
A命令 A_k 把每位员工的工资加上k
S命令 S_k 把每位员工的工资扣除k
F命令 F_k 查询第k多的工资
_(下划线)表示一个空格,I命令、A命令、S命令中的k是一个非负整数,F命令中的k是一个正整数。
在初始时,可以认为公司里一个员工也没有。
I命令的条数不超过100000
A命令和S命令的总条数不超过100
F命令的条数不超过100000
每次工资调整的调整量不超过1000
新员工的工资不超过100000
Output
输出行数为F命令的条数加一。
对于每条F命令,你的程序要输出一行,仅包含一个整数,为当前工资第k多的员工所拿的工资数,
如果k大于目前员工的数目,则输出-1。
输出文件的最后一行包含一个整数,为离开公司的员工的总数。
Sample Input9 10I 60
I 70
S 50
F 2
I 30
S 15
A 5
F 1
F 2
Sample Output10
20
-1
2
题解:
我原本想的是,每位员工工资都减少,那就for循环从1到sz所有的key[]都减去这个k。增加的话也类似于这样
如果工资少于底线的话就删除,我在原来平衡树模板中的del函数中改了一点
最后加上去TLE了
代码:
1 /* 2 注意: 3 1、看平衡树之前你要注意,对于1 3 5 3 2这一组数据。sz的值是4,因为sz保存的是节点种类 4 为什么要这样,因为sz涉及到要为几个点开空间 5 6 2、sizes[x]保存的是以x为树根的子树上节点数量,比如x这颗子树所有节点为1,2,1.那么它的sizes[x]=3 7 而且实际上不会有两个1放在树上。而是给1的权值数组cnt[1]加1 8 9 3、对于1 3 5 3 2这一组数据。sz的值是4。那么1对应位置就是1,3对应位置就是2,5对应位置就是3,2对应位置就是4 10 之后他们所对应的位置都不会改变 11 在平衡树旋转的过程中只是每一个点的儿子节点ch[x][0]和ch[x][1]里面保存的值在改变 12 */ 13 #include<stdio.h> 14 #include<string.h> 15 #include<algorithm> 16 #include<iostream> 17 using namespace std; 18 const int maxn=1e5+10; 19 const int INF=0x3f3f3f3f; 20 int f[maxn],cnt[maxn],ch[maxn][2],sizes[maxn],key[maxn],sz,rt; 21 /* 22 f[i]:i节点的父节点,cnt[i]每个点出现的次数,ch[i][0/1]:0表示左孩子, 23 1表示右孩子, size[i]表示以i为根节点的子树的节点个数 24 key[i]表示点i代表的数的值;sz为整棵树的节点种类数,rt表示根节点 25 */ 26 void clears(int x) //删除x点信息 27 { 28 f[x]=cnt[x]=ch[x][0]=ch[x][1]=sizes[x]=key[x]=0; 29 } 30 bool get(int x) //判断x是父节点的左孩子还是右孩子 31 { 32 return ch[f[x]][1]==x; //返回1就是右孩子,返回0就是左孩子 33 } 34 void pushup(int x) //重新计算一下x这棵子树的节点数量 35 { 36 if(x) 37 { 38 sizes[x]=cnt[x]; 39 if(ch[x][0]) sizes[x]+=sizes[ch[x][0]]; 40 if(ch[x][1]) sizes[x]+=sizes[ch[x][1]]; 41 } 42 } 43 void rotates(int x) //将x移动到他父亲的位置,并且保证树依旧平衡 44 { 45 int fx=f[x],ffx=f[fx],which=get(x); 46 //x点父亲,要接受x的儿子。而且x与x父亲身份交换 47 ch[fx][which]=ch[x][which^1]; 48 f[ch[fx][which]]=fx; 49 50 ch[x][which^1]=fx; 51 f[fx]=x; 52 53 f[x]=ffx; 54 if(ffx) ch[ffx][ch[ffx][1]==fx]=x; 55 56 pushup(fx); 57 pushup(x); 58 } 59 void splay(int x) //将x移动到数根节点的位置,并且保证树依旧平衡 60 { 61 for(int fx; fx=f[x]; rotates(x)) 62 { 63 if(f[fx]) 64 { 65 rotates((get(x)==get(fx))?fx:x); 66 //如果祖父三代连城一条线,就要从祖父哪里rotate 67 //至于为什么要这样做才能最快……可以去看看Dr.Tarjan的论文 68 } 69 } 70 rt=x; 71 } 72 /* 73 将x这个值插入到平衡树上面 74 如果这个值在树上存在过,那就sz不再加1,更新一下权值即可 75 如果这个值在树上不存在,那就sz加1,再更新一下权值 76 77 sz是书上节点种类数 78 sizes[x]是x这棵子树上有多少节点 79 */ 80 void inserts(int x) 81 { 82 if(rt==0) 83 { 84 sz++; 85 key[sz]=x; 86 rt=sz; 87 cnt[sz]=sizes[sz]=1; 88 f[sz]=ch[sz][0]=ch[sz][1]=0; 89 return; 90 } 91 int now=rt,fx=0; 92 while(1) 93 { 94 if(x==key[now]) 95 { 96 cnt[now]++; 97 pushup(now); 98 pushup(fx); 99 splay(now); //splay的过程会rotates now点的所有祖先节点,这个时候它们所有子树权值也更新了 100 return; 101 } 102 fx=now; 103 now=ch[now][key[now]<x]; 104 if(now==0) 105 { 106 sz++; 107 sizes[sz]=cnt[sz]=1; 108 ch[sz][0]=ch[sz][1]=0; 109 ch[fx][x>key[fx]]=sz; //二叉查找树特性”左大右小“ 110 f[sz]=fx; 111 key[sz]=x; 112 pushup(fx); 113 splay(sz); 114 return ; 115 } 116 } 117 } 118 /* 119 有人问: 120 qwq很想知道为什么find操作也要splay操作呢?如果del要用的话直接splay(x)是不是就可以了 121 122 原博客答: 123 呃不不不这个貌似不是随便splay以下就可以的 首先find之后的splay就是将找到的这个点转到根, 124 当然你不加这个应该是也可以,只不过这道题加上的话对于这一堆操作来说比较方便,不过一般来说转一转splay的 125 平衡性会好一点(当然也不要转得太多了就tle了...) 但是del之前直接splay(x)要视情况而定,关键在于分清楚 126 “点的编号”和“点的权值”这两个概念。如果你已经知道了该转的点的编号,当然可以直接splay(x),但是如果你只 127 知道应该splay的点的权值,你需要在树里find到这个权值的点的编号,然后再splay 其实最后splay写起来都是非 128 常灵活的,而且有可能一个点带若干个权之类的。对于初学者的建议就是先把一些最简单的情况搞清楚,比如说一 129 个编号一个权的这种,然后慢慢地多做题就能运用得非常熟练了。最好的方法就是多画画树自己转一转,对之后 130 复杂题目的调试也非常有益 131 132 我说: 133 我在洛谷上得模板题上交了一下rnk里面不带splay(now)的,一共12个样例,就对了两个样例。错了一个样例,其他全TLE 134 135 我解释: 136 为什么作者解释可以删去,但是删过之后还错了。因为它的代码中函数之前是相互联系的 137 就比如它调用rnk(x)函数之后就已经认为x为平衡树树根,然后直接对它进行下一步操作(这个假设在del函数里面) 138 139 如果你光删了rnk(x)里面的splay(),你肯定还要改其他地方代码。。。。。。 140 */ 141 int rnk(int x) //查询x的排名 142 { 143 int now=rt,ans=0; 144 while(1) 145 { 146 if(x<key[now]) now=ch[now][0]; 147 else 148 { 149 ans+=sizes[ch[now][0]]; 150 if(x==key[now]) 151 { 152 splay(now); //这个splay是为了后面函数的调用提供前提条件 153 //就比如pre函数的前提条件就是x(x是我们要求谁的前驱,那个谁就是x)已经在平衡树树根 154 return ans+1; 155 } 156 ans+=cnt[now]; //cnt代表now这个位置值(key[now])出现了几次 157 now=ch[now][1]; 158 } 159 } 160 } 161 int kth(int x) 162 { 163 int now=rt; 164 while(1) 165 { 166 if(ch[now][0] && x<=sizes[ch[now][0]]) 167 { 168 //满足这个条件就说明它在左子树上 169 now=ch[now][0]; 170 } 171 else 172 { 173 int temp=sizes[ch[now][0]]+cnt[now]; 174 if(x<=temp) //这个temp是now左子树权值和now节点权值之和 175 return key[now]; //进到这个判断里面说明他不在左子树又不在右子树,那就是now节点了 176 x-=temp; 177 now=ch[now][1]; 178 } 179 } 180 } 181 int pre()//由于进行splay后,x已经到了根节点的位置 182 { 183 //求x的前驱其实就是求x的左子树的最右边的一个结点 184 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在 185 //x的左子树的最右边的一个结点 186 int now=ch[rt][0]; 187 while(ch[now][1]) now=ch[now][1]; 188 return now; 189 } 190 int next() 191 { 192 //求后继是求x的右子树的最左边一个结点 193 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在 194 //x的右子树的最左边一个结点 195 int now=ch[rt][1]; 196 while(ch[now][0]) now=ch[now][0]; 197 return now; 198 } 199 /* 200 删除操作是最后一个稍微有点麻烦的操作。 201 step 1:随便find一下x。目的是:将x旋转到根。 202 step 2:那么现在x就是根了。如果cnt[root]>1,即不只有一个x的话,直接-1返回。 203 step 3:如果root并没有孩子,就说名树上只有一个x而已,直接clear返回。 204 step 4:如果root只有左儿子或者右儿子,那么直接clear root,然后把唯一的儿子当作根就可以了(f赋0,root赋为唯一的儿子) 205 剩下的就是它有两个儿子的情况。 206 step 5:我们找到新根,也就是x的前驱(x左子树最大的一个点),将它旋转到根。然后将原来x的右子树接到新根的 207 右子树上(注意这个操作需要改变父子关系)。这实际上就把x删除了。不要忘了update新根。 208 */ 209 void del(int x) 210 { 211 rnk(x); 212 // if(cnt[rt]>1)//如果这个位置权值大于1,那就不用删除这个点 213 // { 214 // cnt[rt]--; 215 // pushup(rt); 216 // return; 217 // } 218 if(!ch[rt][0] && !ch[rt][1]) //这个就代表平衡树只有一个节点 219 { 220 clears(rt); 221 rt=0; 222 return; 223 } 224 if(!ch[rt][0]) //只有左儿子,树根只有左儿子那就把树根直接删了就行 225 { //然后左儿子这棵子树变成新的平衡树 226 int frt=rt; 227 rt=ch[rt][1]; 228 f[rt]=0; 229 clears(frt); 230 return; 231 } 232 else if(!ch[rt][1]) //只有右儿子,和上面差不多 233 { 234 int frt=rt; 235 rt=ch[rt][0]; 236 f[rt]=0; 237 clears(frt); 238 return; 239 } 240 int frt=rt; 241 int leftbig=pre(); 242 splay(leftbig); //让前驱做新根 243 ch[rt][1]=ch[frt][1]; //这个frt指向的还是之前的根节点 244 /* 245 看着一点的时候就会发现,数在数组里面的位置一直没有改变,平衡树旋转改变的是根节点儿子数组ch[x][]指向的值 246 */ 247 f[ch[frt][1]]=rt; 248 clears(frt); 249 pushup(rt); 250 } 251 int main() 252 { 253 int n,m,sum,tot=0; 254 scanf("%d%d",&n,&m); 255 inserts(INF); 256 for (int i=1; i<=n; i++) 257 { 258 char type[5]; 259 int k; 260 scanf("%s%d",type,&k); 261 if (type[0]=='I') 262 { 263 inserts(k); 264 tot++; 265 } 266 if (type[0]=='A') 267 { 268 for(int i=1;i<=sz;++i) 269 { 270 if(key[i]!=INF) 271 key[i]+=k; 272 } 273 274 } 275 if (type[0]=='S') 276 { 277 for(int i=1;i<=sz;++i) 278 { 279 if(key[i]!=INF) 280 key[i]-=k; 281 } 282 for(int i=1;i<=sz;++i) 283 { 284 if(key[i]!=INF && key[i]<m) 285 { 286 del(key[i]); 287 } 288 } 289 } 290 if (type[0]=='F') 291 { 292 sum=rnk(INF); 293 if(sum-1<k) 294 { 295 printf("-1\n"); 296 continue; 297 } 298 else 299 { 300 printf("%d\n",kth(sum-k)); 301 } 302 } 303 } 304 sum=rnk(INF); 305 //printf("%d %d\n",tot,sum); 306 printf("%d\n",tot-(sum-1)); 307 return 0; 308 }
正解:
既然不能对每一个员工都这样操作,那么我们在开一个变量delta,用来记录所有的员工的工资的变化量,那么某个员工的实际工资就是x+delta;
然而我们考虑新加入的员工,对她加上历史的delta显然是不合适的;我们可以这样处理:
在平衡树提前插入inf和-inf
I命令:加入一个员工 我们在平衡树中加入k-minn
A命令:把每位员工的工资加上k delta加k即可
S命令:把每位员工的工资扣除k 此时我们就需要考虑会不会导致一大批员工离开;我们插入minn-delta,然后使小于minn-delta的点一起移动到根的右子树的左子树,一举消灭;
F命令:查询第k多的工资 注意是第k多,Splay操作;
代码:
1 /* 2 注意: 3 1、看平衡树之前你要注意,对于1 3 5 3 2这一组数据。sz的值是4,因为sz保存的是节点种类 4 为什么要这样,因为sz涉及到要为几个点开空间 5 6 2、sizes[x]保存的是以x为树根的子树上节点数量,比如x这颗子树所有节点为1,2,1.那么它的sizes[x]=3 7 而且实际上不会有两个1放在树上。而是给1的权值数组cnt[1]加1 8 9 3、对于1 3 5 3 2这一组数据。sz的值是4。那么1对应位置就是1,3对应位置就是2,5对应位置就是3,2对应位置就是4 10 之后他们所对应的位置都不会改变 11 在平衡树旋转的过程中只是每一个点的儿子节点ch[x][0]和ch[x][1]里面保存的值在改变 12 */ 13 #include<stdio.h> 14 #include<string.h> 15 #include<algorithm> 16 #include<iostream> 17 using namespace std; 18 const int maxn=1e5+10; 19 const int INF=1e8; 20 int f[maxn],cnt[maxn],ch[maxn][2],sizes[maxn],key[maxn],sz,rt; 21 /* 22 f[i]:i节点的父节点,cnt[i]每个点出现的次数,ch[i][0/1]:0表示左孩子, 23 1表示右孩子, size[i]表示以i为根节点的子树的节点个数 24 key[i]表示点i代表的数的值;sz为整棵树的节点种类数,rt表示根节点 25 */ 26 void clears(int x) //删除x点信息 27 { 28 f[x]=cnt[x]=ch[x][0]=ch[x][1]=sizes[x]=key[x]=0; 29 } 30 bool get(int x) //判断x是父节点的左孩子还是右孩子 31 { 32 return ch[f[x]][1]==x; //返回1就是右孩子,返回0就是左孩子 33 } 34 void pushup(int x) //重新计算一下x这棵子树的节点数量 35 { 36 if(x) 37 { 38 sizes[x]=cnt[x]; 39 if(ch[x][0]) sizes[x]+=sizes[ch[x][0]]; 40 if(ch[x][1]) sizes[x]+=sizes[ch[x][1]]; 41 } 42 } 43 void rotates(int x) //将x移动到他父亲的位置,并且保证树依旧平衡 44 { 45 int fx=f[x],ffx=f[fx],which=get(x); 46 //x点父亲,要接受x的儿子。而且x与x父亲身份交换 47 ch[fx][which]=ch[x][which^1]; 48 f[ch[fx][which]]=fx; 49 50 ch[x][which^1]=fx; 51 f[fx]=x; 52 53 f[x]=ffx; 54 if(ffx) ch[ffx][ch[ffx][1]==fx]=x; 55 56 pushup(fx); 57 pushup(x); 58 } 59 //void splay(int x) //将x移动到数根节点的位置,并且保证树依旧平衡 60 //{ 61 // for(int fx; fx=f[x]; rotates(x)) 62 // { 63 // if(f[fx]) 64 // { 65 // rotates((get(x)==get(fx))?fx:x); 66 // //如果祖父三代连城一条线,就要从祖父哪里rotate 67 // //至于为什么要这样做才能最快……可以去看看Dr.Tarjan的论文 68 // } 69 // } 70 // rt=x; 71 //} 72 void splay(int x,int goal) 73 { 74 for (int fa; (fa=f[x])!=goal; rotates(x))//这里是不等于 75 if (f[fa]!=goal) 76 rotates(get(x)==get(fa)?fa:x); 77 if (goal==0) rt=x; 78 } 79 /* 80 将x这个值插入到平衡树上面 81 如果这个值在树上存在过,那就sz不再加1,更新一下权值即可 82 如果这个值在树上不存在,那就sz加1,再更新一下权值 83 84 sz是书上节点种类数 85 sizes[x]是x这棵子树上有多少节点 86 */ 87 void Insert(int x) 88 { 89 if(rt==0) 90 { 91 sz++; 92 key[sz]=x; 93 rt=sz; 94 cnt[sz]=sizes[sz]=1; 95 f[sz]=ch[sz][0]=ch[sz][1]=0; 96 return; 97 } 98 int now=rt,fx=0; 99 while(1) 100 { 101 if(x==key[now]) 102 { 103 cnt[now]++; 104 pushup(now); 105 pushup(fx); 106 splay(now,0); //splay的过程会rotates now点的所有祖先节点,这个时候它们所有子树权值也更新了 107 return; 108 } 109 fx=now; 110 now=ch[now][key[now]<x]; 111 if(now==0) 112 { 113 sz++; 114 sizes[sz]=cnt[sz]=1; 115 ch[sz][0]=ch[sz][1]=0; 116 ch[fx][x>key[fx]]=sz; //二叉查找树特性”左大右小“ 117 f[sz]=fx; 118 key[sz]=x; 119 pushup(fx); 120 splay(sz,0); 121 return ; 122 } 123 } 124 } 125 /* 126 有人问: 127 qwq很想知道为什么find操作也要splay操作呢?如果del要用的话直接splay(x)是不是就可以了 128 129 原博客答: 130 呃不不不这个貌似不是随便splay以下就可以的 首先find之后的splay就是将找到的这个点转到根, 131 当然你不加这个应该是也可以,只不过这道题加上的话对于这一堆操作来说比较方便,不过一般来说转一转splay的 132 平衡性会好一点(当然也不要转得太多了就tle了...) 但是del之前直接splay(x)要视情况而定,关键在于分清楚 133 “点的编号”和“点的权值”这两个概念。如果你已经知道了该转的点的编号,当然可以直接splay(x),但是如果你只 134 知道应该splay的点的权值,你需要在树里find到这个权值的点的编号,然后再splay 其实最后splay写起来都是非 135 常灵活的,而且有可能一个点带若干个权之类的。对于初学者的建议就是先把一些最简单的情况搞清楚,比如说一 136 个编号一个权的这种,然后慢慢地多做题就能运用得非常熟练了。最好的方法就是多画画树自己转一转,对之后 137 复杂题目的调试也非常有益 138 139 我说: 140 我在洛谷上得模板题上交了一下rnk里面不带splay(now)的,一共12个样例,就对了两个样例。错了一个样例,其他全TLE 141 142 我解释: 143 为什么作者解释可以删去,但是删过之后还错了。因为它的代码中函数之前是相互联系的 144 就比如它调用rnk(x)函数之后就已经认为x为平衡树树根,然后直接对它进行下一步操作(这个假设在del函数里面) 145 146 如果你光删了rnk(x)里面的splay(),你肯定还要改其他地方代码。。。。。。 147 */ 148 int rnk(int x) //查询x的排名 149 { 150 int now=rt,ans=0; 151 while(1) 152 { 153 if(x<key[now]) now=ch[now][0]; 154 else 155 { 156 ans+=sizes[ch[now][0]]; 157 if(x==key[now]) 158 { 159 splay(now,0); //这个splay是为了后面函数的调用提供前提条件 160 //就比如pre函数的前提条件就是x(x是我们要求谁的前驱,那个谁就是x)已经在平衡树树根 161 return ans+1; 162 } 163 ans+=cnt[now]; //cnt代表now这个位置值(key[now])出现了几次 164 now=ch[now][1]; 165 } 166 } 167 } 168 int kth(int x) 169 { 170 int now=rt; 171 while(1) 172 { 173 if(ch[now][0] && x<=sizes[ch[now][0]]) 174 { 175 //满足这个条件就说明它在左子树上 176 now=ch[now][0]; 177 } 178 else 179 { 180 int temp=sizes[ch[now][0]]+cnt[now]; 181 if(x<=temp) //这个temp是now左子树权值和now节点权值之和 182 return key[now]; //进到这个判断里面说明他不在左子树又不在右子树,那就是now节点了 183 x-=temp; 184 now=ch[now][1]; 185 } 186 } 187 } 188 int pre()//由于进行splay后,x已经到了根节点的位置 189 { 190 //求x的前驱其实就是求x的左子树的最右边的一个结点 191 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在 192 //x的左子树的最右边的一个结点 193 int now=ch[rt][0]; 194 while(ch[now][1]) now=ch[now][1]; 195 return now; 196 } 197 int next() 198 { 199 //求后继是求x的右子树的最左边一个结点 200 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在 201 //x的右子树的最左边一个结点 202 int now=ch[rt][1]; 203 while(ch[now][0]) now=ch[now][0]; 204 return now; 205 } 206 /* 207 删除操作是最后一个稍微有点麻烦的操作。 208 step 1:随便find一下x。目的是:将x旋转到根。 209 step 2:那么现在x就是根了。如果cnt[root]>1,即不只有一个x的话,直接-1返回。 210 step 3:如果root并没有孩子,就说名树上只有一个x而已,直接clear返回。 211 step 4:如果root只有左儿子或者右儿子,那么直接clear root,然后把唯一的儿子当作根就可以了(f赋0,root赋为唯一的儿子) 212 剩下的就是它有两个儿子的情况。 213 step 5:我们找到新根,也就是x的前驱(x左子树最大的一个点),将它旋转到根。然后将原来x的右子树接到新根的 214 右子树上(注意这个操作需要改变父子关系)。这实际上就把x删除了。不要忘了update新根。 215 */ 216 void del(int x) 217 { 218 rnk(x); 219 if(cnt[rt]>1)//如果这个位置权值大于1,那就不用删除这个点 220 { 221 cnt[rt]--; 222 pushup(rt); 223 return; 224 } 225 if(!ch[rt][0] && !ch[rt][1]) //这个就代表平衡树只有一个节点 226 { 227 clears(rt); 228 rt=0; 229 return; 230 } 231 if(!ch[rt][0]) //只有左儿子,树根只有左儿子那就把树根直接删了就行 232 { //然后左儿子这棵子树变成新的平衡树 233 int frt=rt; 234 rt=ch[rt][1]; 235 f[rt]=0; 236 clears(frt); 237 return; 238 } 239 else if(!ch[rt][1]) //只有右儿子,和上面差不多 240 { 241 int frt=rt; 242 rt=ch[rt][0]; 243 f[rt]=0; 244 clears(frt); 245 return; 246 } 247 int frt=rt; 248 int leftbig=pre(); 249 splay(leftbig,0); //让前驱做新根 250 ch[rt][1]=ch[frt][1]; //这个frt指向的还是之前的根节点 251 /* 252 看着一点的时候就会发现,数在数组里面的位置一直没有改变,平衡树旋转改变的是根节点儿子数组ch[x][]指向的值 253 */ 254 f[ch[frt][1]]=rt; 255 clears(frt); 256 pushup(rt); 257 } 258 int id(int x)//查询x的编号 259 { 260 int now=rt; 261 while (1) 262 { 263 if (x==key[now]) return now; 264 else 265 { 266 if (x<key[now]) now=ch[now][0]; 267 else now=ch[now][1]; 268 } 269 } 270 } 271 /* 272 题目中的加减操作都是对于所有员工的,我们不可能对所有的点进行修改,于是我们在开一个变量delta,用来记录 273 所有的员工的工资的变化量,那么某个员工的实际工资就是x+delta; 274 然而我们考虑新加入的员工,对她加上历史的delta显然是不合适的;我们可以这样处理: 275 在平衡树提前插入inf和-inf 276 I命令:加入一个员工 我们在平衡树中加入k-minn 277 A命令:把每位员工的工资加上k delta加k即可 278 S命令:把每位员工的工资扣除k 此时我们就需要考虑会不会导致一大批员工离开;我们插入minn-delta,然后使小于 279 minn-delta的点一起移动到根的右子树的左子树,一举消灭; 280 F命令:查询第k多的工资 注意是第k多,Splay操作; 281 还有一些小细节需要注意,+2-2等等; 282 */ 283 int main() 284 { 285 int n,minn; 286 scanf("%d%d",&n,&minn); 287 int totadd=0,totnow=0,ans=0,delta=0; 288 char opt[10]; int k; 289 Insert(INF); Insert(-INF); 290 for (int i=1; i<=n; i++) 291 { 292 scanf("%s%d",opt,&k); 293 if (opt[0]=='I') 294 { 295 if (k<minn) continue; 296 Insert(k-delta); //后面插入的员工的值都减去了delta,那么后面对所有员工的判断都可以直接让 297 totadd++; //他们的值加上delta与minn进行比较 298 } 299 if (opt[0]=='A') delta+=k; 300 if (opt[0]=='S') 301 { 302 delta-=k; //每一次执行S操作都要进行判断 303 Insert(minn-delta); 304 int a=id(-INF); int b=id(minn-delta); 305 splay(a,0); 306 splay(b,a); //这样的话就是把平衡树上b这个位置移动到了平衡树顶点的位置。而且这个移动 307 ch[ ch[rt][1] ][0]=0; //的过程中b这个顶点的左子树一直在b这个顶点的左子树(不会在旋转过程中变动) 308 del(minn-delta); //最后把顶点(也就是b)的左子树删除了就可以了 309 } 310 if (opt[0]=='F') 311 { 312 totnow=rnk(INF)-2; 313 if (totnow<k) {printf("-1\n"); continue;} 314 int ans=kth(totnow+2-k); 315 printf("%d\n",ans+delta);//最后再加上累加值delta 316 } 317 } 318 totnow=rnk(INF)-2; 319 ans=totadd-totnow; 320 printf("%d",ans); 321 return 0; 322 }