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\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 }

郁闷的出纳员 HYSBZ - 1503的更多相关文章

  1. (WA)BZOJ 1503: [NOI2004]郁闷的出纳员

    二次联通门 : BZOJ 1503: [NOI2004]郁闷的出纳员 /* BZOJ 1503: [NOI2004]郁闷的出纳员 考虑这样一个事实 无论是加或减 都是针对全体人员的 那么只需要记录一个 ...

  2. BZOJ 1503: [NOI2004]郁闷的出纳员

    1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 10526  Solved: 3685[Submit][Stat ...

  3. BZOJ 1503: [NOI2004]郁闷的出纳员 splay

    1503: [NOI2004]郁闷的出纳员 Description OIER公司是一家大型专业化软件公司,有着数以万计的员工.作为一名出纳员,我的任务之一便是统计每位员工的工资.这本来是一份不错的工作 ...

  4. 【BZOJ】【1503】 【NOI2004】郁闷的出纳员

    Splay Splay的模板题吧……妥妥的序列操作= =(好像有段时间没写过这种纯数据结构题了……) /************************************************ ...

  5. bzoj 1503: [NOI2004]郁闷的出纳员 Treap

    1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 6263  Solved: 2190[Submit][Statu ...

  6. 1503: [NOI2004]郁闷的出纳员 (SBT)

    1503: [NOI2004]郁闷的出纳员 http://www.lydsy.com/JudgeOnline/problem.php?id=1503 Time Limit: 5 Sec  Memory ...

  7. BZOJ 1503 郁闷的出纳员 (treap)

    1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 13370  Solved: 4808[Submit][Stat ...

  8. bzoj 1503: [NOI2004]郁闷的出纳员 -- 权值线段树

    1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec  Memory Limit: 64 MB Description OIER公司是一家大型专业化软件公司,有着数以万计的员 ...

  9. 洛谷 1486/BZOJ 1503 郁闷的出纳员

    1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 13866  Solved: 5069[Submit][Stat ...

随机推荐

  1. upload-labs 1-21关通关记录

    0x01: 检查源代码,发现JS前端验证,关闭JS即可连接,或者手动添加.php,或者上传1.jpg,再抓包修改为php 0X02: if (($_FILES['upload_file']['type ...

  2. 关于JDK15的简单理解

    一.为什么要了解JDK15? 2020年9月15日,Oracle官方发布了JDK15版本,及时关注官方的更新动态,可以让我们在日常开发中更合理的选择更加优秀的工具方法,避免使用一些过时的或一些即将被删 ...

  3. 学习Java第三天

    方法重载:同一个类,方法名相同,参数不同(个数不同,类型不同,顺序不同),判断是否重载,只看方法名和参数,跟返回值无关. IDEA查看方法源代码:Crtl + 鼠标左键 进制表示 Java数值默认为十 ...

  4. Django Signals

    信号 Django中提供了"信号调度",用于在框架执行操作时解耦.通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者. Django内置的信号 Model si ...

  5. 六个你不知道的PR快捷键,拯救你的剪辑效率

    5G时代到来,会剪辑视频的人,无论在校园还是未来步入职场都很吃香.对于普通人来说,视频处理也成为了一个通用技能.PR是我们大多数人剪辑中,经常会用到的剪辑工具,之前的文章中已经给大家总结了pr的一些提 ...

  6. Xamarin.Forms: 无限滚动的ListView(懒加载方式)

    说明 在本博客中,学习如何在Xamarin.Forms应用程序中设计一个可扩展的无限滚动的ListView.这个无限滚动函数在默认的Xamarin.Forms不存在,因此我们需要为此添加插件.在这里我 ...

  7. (转载)微软数据挖掘算法:Microsoft 神经网络分析算法原理篇(9)

    前言 本篇文章继续我们的微软挖掘系列算法总结,前几篇文章已经将相关的主要算法做了详细的介绍,我为了展示方便,特地的整理了一个目录提纲篇:大数据时代:深入浅出微软数据挖掘算法总结连载,有兴趣的童鞋可以点 ...

  8. 正则re高级用法

    search 需求:匹配出文章阅读的次数 #coding=utf-8 import re ret = re.search(r"\d+", "阅读次数为 9999" ...

  9. 洛谷P2865

    感觉此题可作为严格次短路的模板,因此来写一写 Description 给定 \(n\) 个点,\(r\) 条双向道路,求从 \(1\) 号点到 \(n\) 号点的严格次短路 Solution 维护两个 ...

  10. IntelliJ IDEA 20 岁了!20 年前的第 1 版曝光…

    IntelliJ IDEA 最近发布了 20 周年庆典: https://www.jetbrains.com/lp/intellijidea-20-anniversary/ IntelliJ IDEA ...