郁闷的出纳员 HYSBZ - 1503
Input
Output
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的更多相关文章
- (WA)BZOJ 1503: [NOI2004]郁闷的出纳员
二次联通门 : BZOJ 1503: [NOI2004]郁闷的出纳员 /* BZOJ 1503: [NOI2004]郁闷的出纳员 考虑这样一个事实 无论是加或减 都是针对全体人员的 那么只需要记录一个 ...
- BZOJ 1503: [NOI2004]郁闷的出纳员
1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 10526 Solved: 3685[Submit][Stat ...
- BZOJ 1503: [NOI2004]郁闷的出纳员 splay
1503: [NOI2004]郁闷的出纳员 Description OIER公司是一家大型专业化软件公司,有着数以万计的员工.作为一名出纳员,我的任务之一便是统计每位员工的工资.这本来是一份不错的工作 ...
- 【BZOJ】【1503】 【NOI2004】郁闷的出纳员
Splay Splay的模板题吧……妥妥的序列操作= =(好像有段时间没写过这种纯数据结构题了……) /************************************************ ...
- bzoj 1503: [NOI2004]郁闷的出纳员 Treap
1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 6263 Solved: 2190[Submit][Statu ...
- 1503: [NOI2004]郁闷的出纳员 (SBT)
1503: [NOI2004]郁闷的出纳员 http://www.lydsy.com/JudgeOnline/problem.php?id=1503 Time Limit: 5 Sec Memory ...
- BZOJ 1503 郁闷的出纳员 (treap)
1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 13370 Solved: 4808[Submit][Stat ...
- bzoj 1503: [NOI2004]郁闷的出纳员 -- 权值线段树
1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec Memory Limit: 64 MB Description OIER公司是一家大型专业化软件公司,有着数以万计的员 ...
- 洛谷 1486/BZOJ 1503 郁闷的出纳员
1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 13866 Solved: 5069[Submit][Stat ...
随机推荐
- jenkins 构建历史 显示版本号
0 jenkins 安装此插件: 此插件名为 " groovy postbuild " 1 效果图: 2 安装插件: 系统管理 --> 插件管理 --> 可选 ...
- Linux学习笔记 | 配置nginx
目录 一.Nginx概述 二.why Nginx? 三.Linux安装Nginx APT源安装 官网源码安装 四.nginx相关文件的配置 html文件:/var/www/html/index.htm ...
- 安装MySQL数据库(在Windows下通过zip压缩包安装)
安装MySQL 这里建议大家使用压缩版,安装快,方便.不复杂. 软件下载 mysql5.7 64位下载地址: https://dev.mysql.com/get/Downloads/MySQL-5.7 ...
- a[i][j] 和 a[j][i] 有什么区别?
本文以一个简单的程序开头--数组赋值: int LEN = 10000;int[][] arr = new int[LEN][LEN]; for (int i = 0; i < LEN; i++ ...
- Lnux:实验 Linux C 编程
实验题目: 实验 3 Linux C 编程 实验目的和要求: 熟悉 Linux 操作系统环境 在 Linux 下编写.执行简单的 C 程序 用 C 语言写自己的 Linux 命令 实验过程: 认真 ...
- Eclipse中的可视化图形界面设计插件windowbuilder
对于eclipse平台上的可视化开发工具插件,有windowbuilder.visual editor等,今天就对windowbuilder说明: WindowBuilder功能特性等介绍,参考如下网 ...
- DOI技术扫盲一
DOI: desktop office intergration 桌面办公软件集成简单的将,就是我们在Windows桌面中打开的办公软件(如:word,excel,pdf等等)可以在SAP系统进 ...
- WIFI 国家码和信道划分
前言 网上百度了很多资料,都没有找到国家码对应支持哪些信道的资料,无奈只能qiang到谷歌,分享给大家完整的WIFI 国家码和信道划分. 安卓WIFI国家码的影响 android中设置wifi国家码的 ...
- 三分钟学会 ASP.NET Core WebApi使用Swagger生成api说明文档
什么是Swagger?为啥要用Swagger? Swagger可以从不同的代码中,根据注释生成API信息,Swagger拥有强大的社区,并且对于各种语言都支持良好,有很多的工具可以通过swagger生 ...
- (转)iOS工具--CocoaPods 安装使用总结
本文转载自:CocoaPods 安装使用总结(最新) 一.前言 关于什么是CocoaPods,使用CocoaPods的好处等问题本文不做说明,本文只是CocoaPods的安装和使用教程.根据此教程可以 ...