P1486 [NOI2004]郁闷的出纳员 \color{green}{\text{P1486\ \ \ \ \ [NOI2004]郁闷的出纳员}} P1486 [NOI2004]郁闷的出纳员
【题意】:
\color{blue}{\text{【题意】:}}
【题意】: OIER公司
是一家大型专业化软件公司,有着数以万计的员工。作为一名 出纳员
,我的任务之一便是统计每位员工的工资。这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好,就可能把每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把他们的工资扣除一个相同的量。我真不知道除了调工资他还做什么其它事情。
工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。每位员工的工资下界都是统一规定的。每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘了一位新员工,我就得为他新建一个工资档案。
老板经常到我这边来询问工资情况,他并不问具体某位员工的工资情况,而是问现在工资第 k k k 多 \color{red}{\text{多}} 多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后告诉他答案。
好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样,不是很困难吧?
如果某个员工的初始工资低于最低工资标准,那么将不计入最后的答案内。
【思路】:
\color{blue}{\text{【思路】:}}
【思路】: 如果没有 A
和 S
操作,这题就是一道裸的平衡树的题目。
由于上面两个操作的存在,使得我们在执行它们的时候,时间复杂度会变成 O ( N × log N ) O(N \times \log N) O(N×logN),其中 N N N 是当前员工个数。只要出题人想卡,就是上帝来了也救不了啊!!!
怎么办?我们沿用 差分 \color{red}{\text{差分}} 差分 的思想,既然大家的工资是同时加,同时减,那么我们工资之间的相对差一直不变。
也就是说,我们可以用一个变量
delta
\text{delta}
delta 表示从第
1
1
1 个操作到现在,工资一共涨了多少(
delta
\text{delta}
delta 为负代表跌)。然后执行 A
和 S
操作时,我们只需改变
delta
\text{delta}
delta 即可。
等等,由于最低工资是一直不变的,那么员工工资的变化不是会导致员工工资和最低工资之间的相对差发生变化吗?
是的,所以我们在修改 delta \text{delta} delta 的同时,也要修改最低工资 minn \text{minn} minn。
最后一个问题:如何删除?如果您用其它平衡树,可能有很厉害的方法。但是如果您一定要用 Treap
该怎么办呢?
既然没有其它所谓的厉害的方法,那么我们可以考虑直接 暴 力 \color{red}{暴力} 暴力 啊!!!即直接需要删除一个就删除一个。
但是,这样不会 TLE
吗?要是一次要删除很多很多,
O
(
N
)
O(N)
O(N) 个元素怎么办?那不是还是 TLE
到飞起吗?
注意到删除的前提是加入(没有加入哪有删除),既然加入的元素个数最多是
1
×
1
0
5
1 \times 10^5
1×105 个,那么删除的数的总个数顶多不也就是
1
×
1
0
5
1\times 10^5
1×105 个吗?哪怕你一次就删除
1
×
1
0
5
−
1
1 \times 10^5 -1
1×105−1 个元素,总共最多也就删除
1
×
1
0
5
1\times 10^5
1×105 个元素,即用于删除操作的时间最多是
O
(
1
×
1
0
5
×
log
(
1
×
1
0
5
)
)
O(1 \times 10^5 \times \log (1 \times 10^5))
O(1×105×log(1×105)) 的,不会 TLE
(除非写炸了)。
想到这里,基本就只剩打模板了。但是仍然有几点要注意的:
- 输出的是第 k k k 大(第 N − k + 1 N-k+1 N−k+1小),不是第 k k k 小。
- 输出时不能直接输出第 k k k 大,输出是第 k k k 大元素的值 + delta + \text{delta} +delta。
【代码】: \color{blue}{\text{【代码】:}} 【代码】:
const int N=1e5+100;
int ch[N][2],v[N],size[N];
int pri[N],cnt[N],root,tot;
inline void updata(int u){
size[u]=size[ch[u][0]]+size[ch[u][1]]+cnt[u];
}
inline void rodata(int &u,int d){
register int son=ch[u][d];
ch[u][d]=ch[son][d^1];
ch[son][d^1]=u;updata(u);
updata(u=son);return;
}//d=0:左儿子旋为根
//d=1:右儿子旋为根
void insert(int &u,int num){
if (u==0){
u=(++tot);cnt[u]=size[u]=1;
v[u]=num;pri[u]=rand();return;
}
++size[u];//无论如何都加一
if (v[u]==num){++cnt[u];return;}
int d=num>v[u];insert(ch[u][d],num);
if (pri[u]>pri[ch[u][d]]) rodata(u,d);
}
void Delete(int &u,int num){
if (u==0) return;
if (v[u]==num){
if (cnt[u]>1){--cnt[u];--size[u];return;}
register int d=pri[ch[u][0]]<pri[ch[u][1]];
if (ch[u][0]==0||ch[u][1]==0) u=ch[u][0]+ch[u][1];
else{rodata(u,d^1);Delete(u,num);return;}
}
else{--size[u];Delete(ch[u][v[u]<num],num);}
}
int query_rank(int u,int num){
if (u==0) return 0;//避免题目中可能的错误
if (v[u]==num) return size[ch[u][0]]+1;
if (v[u]>num) return query_rank(ch[u][0],num);
return size[ch[u][0]]+cnt[u]+query_rank(ch[u][1],num);
}
int query_kth(int u,int num){
if (num==0) return 0;//判错,避免题目中可能的错误
if (num<=size[ch[u][0]]) return query_kth(ch[u][0],num);
if (num>size[ch[u][0]]&&num<=size[ch[u][0]]+cnt[u]) return v[u];
return query_kth(ch[u][1],num-size[ch[u][0]]-cnt[u]);
}
const int inf=0x7fffffff;
int prenum(int u,int num){
if (u==0) return -inf;//避免题目中可能的错误
if (v[u]>num) return prenum(ch[u][0],num);
return max(prenum(ch[u][1],num),v[u]);
}//查找<=num的最大的数字(前缀)
int nxtnum(int u,int num){
if (u==0) return inf;//避免题目中可能的错误
if (v[u]<num) return nxtnum(ch[u][1],num);
return min(nxtnum(ch[u][0],num),v[u]);
}//查找>=num的最小的数字(后缀)
int n,minn,delta,leave,num;
int main(){
n=read();minn=read();
delta=root=tot=0;//初始化
srand(time(NULL));//随机数种子
for(int i=1,u,a1,a2;i<=n;i++){
char opt;cin>>opt;u=read();
switch(opt){
case 'A':minn-=u;delta+=u;break;
case 'F':if (num<u) printf("-1\n");//人数不够,输出-1
else printf("%d\n",query_kth(root,num-u+1)+delta);
break;//注意switch-case语句必须要有break!!!
case 'I':if (u>=minn+delta){insert(root,u-delta);num++;}
break;//注意初始工资>=最低工资(minn+delta)才算入
case 'S':delta-=u;minn+=u;a1=minn-1;a2=0;
while (prenum(root,a1)!=-inf){
a2=a1;a1=prenum(root,a1);
Delete(root,prenum(root,a2));
--num;++leave;
}//删除愤怒离开的员工
break;//Don't forget it!
}
}
printf("%d",leave);
return 0;
}
本代码没有任何的反作弊系统(本两句除外),请读者放心。
read() 函数就是快读函数,我就不给了。