郁闷的出纳员 HYSBZ

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 10
I 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
");
296                 continue;
297             }
298             else
299             {
300                 printf("%d
",kth(sum-k));
301             }
302         }
303     }
304     sum=rnk(INF);
305     //printf("%d %d
",tot,sum);
306     printf("%d
",tot-(sum-1));
307     return 0;
308 }
View Code

正解:

既然不能对每一个员工都这样操作,那么我们在开一个变量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
"); continue;}
314             int ans=kth(totnow+2-k);
315             printf("%d
",ans+delta);//最后再加上累加值delta
316         }
317     }
318     totnow=rnk(INF)-2;
319     ans=totadd-totnow;
320     printf("%d",ans);
321     return 0;
322 }
原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/12218424.html