【例题】【Splay】NKOJ1921 郁闷的出纳员

NKOJ1921 郁闷的出纳员
时间限制 : 10000 MS 空间限制 : 65536 KB

问题描述
OIER公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的工资。这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好,就可能把每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把他们的工资扣除一个相同的量。我真不知道除了调工资他还做什么其它事情。
   工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。每位员工的工资下界都是统一规定的。每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘了一位新员工,我就得为他新建一个工资档案。
   老板经常到我这边来询问工资情况,他并不问具体某位员工的工资情况,而是问现在工资第k多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后告诉他答案。
   好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样,不是很困难吧?

输入格式
第一行有两个非负整数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是一个正整数。
在初始时,可以认为公司里一个员工也没有。

输出格式
输出文件的行数为F命令的条数加一。
对于每条F命令,你的程序要输出一行,仅包含一个整数,为当前工资第k多的员工所拿的工资数,如果k大于目前员工的数目,则输出-1。
输出文件的最后一行包含一个整数,为离开公司的员工的总数。

样例输入
9 10
I 60
I 70
S 50
F 2
I 30
S 15
A 5
F 1
F 2

样例输出
10
20
-1
2

提示
I命令的条数不超过100000
A命令和S命令的总条数不超过100
F命令的条数不超过100000
每次工资调整的调整量不超过1000
新员工的工资不超过100000

来源 NOI2004

思路:
设实际工资=存储的工资-del
(1)、加人:特判后insert,好像“如果某员工的初始工资低于工资下界,他将立刻离开公司”并不算作离开公司
(2)、加工资:将del减小
(3)、减工资:将del增加。
删除操作:找到第一个刚刚超过界限的人做根,将根的左子改为0,更新根的size。注意所有人都离开时特殊处理
(4)、找第k大:直接找

#include<cstdio>
#include<iostream>
using namespace std;
const int need=100004;
typedef int int_[need];

//.............................................
inline void inc(char &d)
{
    d=getchar();while(d==' '||d==10) d=getchar();
}
inline void in_(int &d)
{
    char t=getchar();bool mark=0;
    while(t<'0'||t>'9') {if(t=='-') mark=1;t=getchar();}
    for(d=0;!(t<'0'||t>'9');t=getchar()) d=(d<<1)+(d<<3)+t-'0';if(mark) d=-d;
}
inline void out_(int x)
{
    if(x<0) {x=-x;putchar('-');}
    if(x>=10) out_(x/10);
    putchar(x%10+'0');
}
//............................................. 
int k,n,del,ans;
int root,tot;
int_ fa,ls,rs,si,val;

#define NBHB(x) si[x]=si[ls[x]]+si[rs[x]]+1

inline void sr(int x)
{
    int y=fa[x],z=fa[y];
    ls[z]==y ? ls[z]=x :rs[z]=x; fa[x]=z;
    ls[y]=rs[x]; fa[rs[x]]=y;
    rs[x]=y; fa[y]=x;
    NBHB(y),NBHB(x);
}
inline void sl(int x)
{
    int y=fa[x],z=fa[y];
    ls[z]==y ? ls[z]=x :rs[z]=x; fa[x]=z;
    rs[y]=ls[x]; fa[ls[x]]=y;
    ls[x]=y; fa[y]=x;
    NBHB(y),NBHB(x);
}
inline void splay(int x)
{
    int y;
    while(y=fa[x])
    {
        if(ls[y]==x) sr(x);
        else sl(x);
    }
    root=x;
}
inline void insert(int d)
{
    if(root==0)
    {
        val[root=1]=d;
        si[1]=1;
        ls[1]=rs[1]=fa[1]=0;
        tot++;
        return ;
    }
    tot++;
    int p=root;
    while(true)
    {
        if(d<val[p])
         if(ls[p]) p=ls[p];
         else {ls[p]=tot;break;}
        else 
         if(rs[p]) p=rs[p];
         else {rs[p]=tot;break;}
    }
    si[tot]=1;fa[tot]=p;val[tot]=d;
    splay(tot);
}
//............................................. 
int d;

int find(int p)//找第一个大于等于d的节点 
{
    if(p==0) return -1;//上一个节点没有左右儿子,返回-1 
    if(val[p]<d) return find(rs[p]);
    if(val[p]>=d) 
    {
        int kk=find(ls[p]);
        if(kk!=-1&&val[kk]>=d) return kk;
        else return p;
    }
}
void deal(int dd)
{
    d=dd;
    int kk=find(root);
    if(kk==-1) 
    {
        ans+=si[root];
        root=0;
    }
    else 
    {
        splay(kk);
        ans+=si[ls[kk]];
        ls[kk]=0,si[kk]=si[rs[kk]]+1;
    }
}
int getk(int k)
{
    if(k>si[root]) return -1;
    int p=root;
    while(true)
    {
        if(si[rs[p]]>=k) p=rs[p];
        else if(si[rs[p]]==k-1) return val[p];
        else {k-=si[rs[p]]+1;p=ls[p];}
    }
} 
//............................................. 

int main()
{
    scanf("%d%d",&n,&k);
    char c;int b;
    for(int i=1,kk;i<=n;i++)
    {
        inc(c);in_(b);
        if(c=='I')
        {
            if(b>=k)insert(b+del);
        }
        else if(c=='A') del-=b;
        else if(c=='S')
        {
            del+=b;
            deal(k+del);
        }
        else
        {
            kk=getk(b);
            out_(kk==-1 ? kk :kk-del),putchar(10);
        }
    }
    out_(ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值