bzoj 1503 郁闷的出纳员 Splay入门

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1503
题意:

写一种数据结构,支持以下几种操作:
I 命令 新建一个工资档案,初始工资为 k。
如果某员工的初始工资低于工资下界,他将立刻离开公司。
A 命令 把每位员工的工资加上 k
S 命令 把每位员工的工资扣除 k
F 命令 查询第 k 多的工资

分析:
可以参考IOI2009 国家集训队作业 平衡树 罗穗骞

把每位员工的工资加上k和把每位员工的工资扣除k这两种操作用一个变量记录下来,没有必要把所有人的工资都访问一遍。在新建一个工资档案时,需要稍微处理一下。所以本题本质上只要支持三种操作:插入一个元素,删除一个元素,询问第 k 小的元素,判断最小的元素是否小于下界。而判断最小的元素是否小于下界可以看成是询问第 1 小元素。
用平衡树保存所有员工的工资,对于插入一个元素,删除一个元素,询问第k 小的元素等操作,都可以很好的解决。跟例1 一样,可以使用点型动态平衡树 ,每个节点保存当前区间有多少个元素。每次询问第 k 小的元素,就可以根据左右子树的节点数目决定第 k 小元素是在左子树还是右子树。每一次操作的时间复杂度都是 O(logn)。

代码:

#include<cstdio>
#include<cstdlib>
const int inf  = ~0u>>2;
#define L ch[x][0]
#define R ch[x][1]
#define KT (ch[ ch[rt][1] ][0])
const int maxn = 1e5+9;

struct SplayTree {
    int sz[maxn];
    int ch[maxn][2];
    int pre[maxn];
    int rt,top;
    inline void up(int x) {
        sz[x]  = cnt[x]  + sz[ L ] + sz[ R ];
    }
    inline void Rotate(int x,int f) {
        int y=pre[x];
        ch[y][!f] = ch[x][f];
        pre[ ch[x][f] ] = y;
        pre[x] = pre[y];
        if(pre[x]) ch[ pre[y] ][ ch[pre[y]][1] == y ] =x;
        ch[x][f] = y;
        pre[y] = x;
        up(y);
    }
    inline void Splay(int x,int goal) { //将x旋转到goal的下面
        while(pre[x] != goal) {
            if(pre[pre[x]] == goal) Rotate(x , ch[pre[x]][0] == x);
            else   {
                int y=pre[x],z=pre[y];
                int f = (ch[z][0]==y);
                if(ch[y][f] == x) Rotate(x,!f),Rotate(x,f);
                else Rotate(y,f),Rotate(x,f);
            }
        }
        up(x);
        if(goal==0) rt=x;
    }
    inline void RTO(int k,int goal) { //将第k位数旋转到goal的下面
        int x=rt;
        while(sz[ L ] != k-1) {
            if(k < sz[ L ]+1) x=L;
            else {
                k-=(sz[ L ]+1);
                x = R;
            }
        }
        Splay(x,goal);
    }

    inline void Newnode(int &x,int c,int f) {
        x=++top;
        L = R = 0;
        pre[x] = f;
        sz[x]=1;
        cnt[x]=1;
        val[x] = c;
    }
    inline void init() {
        ch[0][0]=ch[0][1]=pre[0]=sz[0]=0;
        rt=top=0;
        cnt[0]=0;
    }
    inline void Insert(int &x,int key,int f) {
        if(!x) {
            Newnode(x,key,f);
            Splay(x,0);//注意插入完成后splay
            return ;
        }
        if(key==val[x]) {
            cnt[x]++;
            sz[x]++;
            Splay(x,0);//注意插入完成后splay
            return ;
        } else if(key<val[x]) {
            Insert(L,key,x);
        } else {
            Insert(R,key,x);
        }
        up(x);
    }
    void Del_root() { //删除根节点
        int t=rt;
        if(ch[rt][1]) {
            rt=ch[rt][1];
            RTO(1,0);
            ch[rt][0]=ch[t][0];
            if(ch[rt][0]) pre[ch[rt][0]]=rt;
        } else rt=ch[rt][0];
        pre[rt]=0;
        up(rt);
    }
    void findpre(int x,int key,int &ans) { //找前驱节点
        ans=-inf;
        while(x) {
            if(val[x]<key) {
                if(val[x]>ans)ans=val[x];
                x=R;
            } else x=L;
        }
    }
    void findsucc(int x,int key,int &ans) { //找后继节点
        ans=inf;
        while(x) {
            if(val[x]>key) {
                if(val[x]<ans)ans=val[x];
                //  ans=min(ans,val[x]);
                x=L;
            } else x=R;
        }
    }
    inline int find_kth(int x,int k) { //第k小的数
        if(k<sz[L]+1) {
            return find_kth(L,k);
        } else if(k > sz[ L ] + cnt[x] )
            return find_kth(R,k-sz[L]-cnt[x]);
        else {
            Splay(x,0);
            return val[x];
        }
    }

    void del(int &x,int f) { //删除小于lim的所有的数所在的节点
        if(!x) return ;
        if(val[x]>=lim) {
            del(L,x);
        } else {
            sum+=sz[L]+cnt[x];
            x=R;
            pre[x]=f;
            if(f==0)  rt=x;
            del(x,f);
        }
        if(x)  up(x);
    }

    void solve(int n,int lim0) {
       char op[5];
       int x;
       lim=lim0;
       sum=0;
       init();
       while(n--){
            scanf("%s%d",op,&x);
            if(op[0]=='I'){
                if(x<lim0)continue;
                Insert(rt,x+lim-lim0,0);
            }
            else if(op[0]=='A'){
                lim-=x;
            }
            else if(op[0]=='S'){
                lim+=x;
                if(sz[rt])del(rt,0);
            }
            else{
                if(x>sz[rt])puts("-1");
                else printf("%d\n",find_kth(rt,sz[rt]-x+1)-lim+lim0);
            }
       }
       printf("%d\n",sum);
    }
    int cnt[maxn];
    int val[maxn];
    int lim,sum;
} spt;

int main() {
    int n,lim;
    while(~scanf("%d%d",&n,&lim)) {
        spt.solve(n,lim);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值