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. 基于B/S架构的在线考试系统的设计与实现

    前言 这个是我的Web课程设计,用到的主要是JSP技术并使用了大量JSTL标签,所有代码已经上传到了我的Github仓库里,地址:https://github.com/quanbisen/online ...

  2. Java 用java GUI写一个贪吃蛇小游戏

    目录 主要用到 swing 包下的一些类 上代码 游戏启动类 游戏数据类 游戏面板类 代码地址 主要用到 swing 包下的一些类 JFrame 窗口类 JPanel 面板类 KeyListener ...

  3. 大文件上传FTP

    需求 将本地大文件通过浏览器上传到FTP服务器. 原有方法 将本地文件整个上传到浏览器,然后发送到node服务器,最后由node发送到FTP服务器. 存在问题 浏览器缓存有限且上传速率受网速影响,当文 ...

  4. 深入理解Redis之简单动态字符串

    目录 SDS SDS与C字符串的区别 SDS获取字符串长度复杂度为O(1),C字符串为O(N) SDS杜绝了缓存区溢出 减少修改字符串时带来的内存重分配次数 二进制安全 Redis没有直接使用C语言传 ...

  5. oracle优化求生指南脚本记录

    1.查找未使用索引 /* Formatted on 2020/5/12 下午 03:32:39 (QP5 v5.163.1008.3004) */ WITH IN_PLAN_OBJECTS AS (S ...

  6. [USACO2011 Feb] Cow Line

    原题链接https://www.lydsy.com/JudgeOnline/problem.php?id=3301 康拓展开和逆展开的模板题. #include<iostream> #in ...

  7. Java 8中字符串拼接新姿势:StringJoiner

    介绍 StringJoiner是java.util包中的一个类,用于构造一个由分隔符分隔的字符序列(可选),并且可以从提供的前缀开始并以提供的后缀结尾.虽然这也可以在StringBuilder类的帮助 ...

  8. Redis 实战 —— 06. 持久化选项

    持久化选项简介 P61 Redis 提供了两种不同的持久化方法来将数据存储到硬盘里面. RDB(redis database):可以将某一时刻的所有数据都写入硬盘里面.(保存的是数据本身) AOF(a ...

  9. .NET Core部署到linux(CentOS)最全解决方案,高阶篇(Docker+Nginx 或 Jexus)

    在前两篇: .NET Core部署到linux(CentOS)最全解决方案,常规篇 .NET Core部署到linux(CentOS)最全解决方案,进阶篇(Supervisor+Nginx) 我们对. ...

  10. Linux系统使用lvm扩展根分区

    Linux系统使用lvm扩展根分区 背景:买的云主机虚拟机封装镜像是40G的系统盘,后期适用不规范或者其他需求需要扩展系统盘,而非挂载在一个盘至新建目录. 1.原本目录磁盘等信息: 2.使用vgdis ...