NOI2004 郁闷的出纳员

本文分享了一道关于Splay树的数据结构题目的解题思路与实现细节,通过对相同数值处理及区间删除等难点的剖析,辅以完整代码示例,帮助读者掌握Splay树的应用技巧。

往事不堪回首。。

这个题目花了2天时间才A了。

思路照搬SnowyJone大牛:http://www.cnblogs.com/w007878/p/3453023.html  Orz。。

此处可找到数据:http://tieba.baidu.com/p/1217076472

真是虐心的两天,整个人都瘦了。。

当然也算值了,为的就是splay的模板大计!

 

操作中较为难办的就是相同数和删除区间。

相同数解决方法:保证每个数都不同,用num记录其个数

删除区间[l, r]:实际上是将l的前驱p,r的后继q,将p移动到q的子节点,直接删除p的右子树,然后更新即可。相比对序列操作的l 和 r可能不存在,需要特殊处理一下。

再者就是各种细节:

  1. 初始工资小于MIN的不算踢出的。。

  2. 各种PUSHUP,PUSHDOWN注意。

 

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#include <utility>
#include <vector>
#include <queue>
#include <map>
#include <set>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
#define INF 0x3f3f3f3f
#define MAXN 100005

using namespace std;

int cnt, rt;
int Add[MAXN],ans;

struct Tree{
    int key, num, size, fa, son[2];
}T[MAXN];

inline void PushUp(int x)
{
    T[x].size=T[T[x].son[0]].size+T[T[x].son[1]].size+T[x].num;
}

inline void PushDown(int x)
{
    if(Add[x])
    {
        if(T[x].son[0])
        {
            T[T[x].son[0]].key+=Add[x];
            Add[T[x].son[0]]+=Add[x];
        }
        if(T[x].son[1])
        {
            T[T[x].son[1]].key+=Add[x];
            Add[T[x].son[1]]+=Add[x];
        }
        Add[x]=0;
    }
}

inline int Newnode(int key, int fa) //新建一个节点并返回
{
    ++cnt;
    T[cnt].key=key;
    T[cnt].num=T[cnt].size=1;
    T[cnt].fa=fa;
    T[cnt].son[0]=T[cnt].son[1]=0;
    return cnt;
}

inline void Rotate(int x, int p) //0左旋 1右旋
{
    int y=T[x].fa;
    PushDown(y);
    PushDown(x);
    T[y].son[!p]=T[x].son[p];
    T[T[x].son[p]].fa=y;
    T[x].fa=T[y].fa;
    if(T[x].fa)
        T[T[x].fa].son[T[T[x].fa].son[1] == y]=x;
    T[x].son[p]=y;
    T[y].fa=x;
    PushUp(y);
    PushUp(x);
}

void Splay(int x, int To) //将x节点移动到To的子节点中
{
    while(T[x].fa != To)
    {
        if(T[T[x].fa].fa == To)
            Rotate(x, T[T[x].fa].son[0] == x);
        else
        {
            int y=T[x].fa, z=T[y].fa;
            int p=(T[z].son[0] == y);
            if(T[y].son[p] == x)
                Rotate(x, !p), Rotate(x, p); //之字旋
            else
                Rotate(y, p), Rotate(x, p); //一字旋
        }
    }
    if(To == 0) rt=x;
}

int GetPth(int p, int To) //返回第p小的节点 并移动到To的子节点中
{
    if(!rt || p > T[rt].size) return 0;
    int x=rt;
    while(x)
    {
        PushDown(x);
        if(p >= T[T[x].son[0]].size+1 && p <= T[T[x].son[0]].size+T[x].num)
            break;
        if(p > T[T[x].son[0]].size+T[x].num)
        {
            p-=T[T[x].son[0]].size+T[x].num;
            x=T[x].son[1];
        }
        else
            x=T[x].son[0];
    }
    Splay(x, 0);
    return x;
}

int Find(int key) //返回值为key的节点 若无返回0 若有将其转移到根处
{
    if(!rt) return 0;
    int x=rt;
    while(x)
    {
        PushDown(x);
        if(T[x].key == key) break;
        x=T[x].son[key > T[x].key];
    }
    if(x) Splay(x, 0);
    return x;
}

int Prev() //返回根节点的前驱 非重点
{
    if(!rt || !T[rt].son[0]) return 0;
    int x=T[rt].son[0];
    while(T[x].son[1])
    {
        PushDown(x);
        x=T[x].son[1];
    }
    Splay(x, 0);
    return x;
}

int Succ() //返回根结点的后继 非重点
{
    if(!rt || !T[rt].son[1]) return 0;
    int x=T[rt].son[1];
    while(T[x].son[0])
    {
        PushDown(x);
        x=T[x].son[0];
    }
    Splay(x, 0);
    return x;
}

void Insert(int key) //插入key值
{
    if(!rt)
        rt=Newnode(key, 0);
    else
    {
        int x=rt, y=0;
        while(x)
        {
            PushDown(x);
            y=x;
            if(T[x].key == key)
            {
                T[x].num++;
                T[x].size++;
                break;
            }
            T[x].size++;
            x=T[x].son[key > T[x].key];
        }
        if(!x)
            x=T[y].son[key > T[y].key]=Newnode(key, y);
        Splay(x, 0);
    }
}

void Delete(int l, int r) //删除值在[l, r]中的节点
{
    if(!Find(l)) Insert(l), ans--;
    int p=Prev();
    if(!Find(r)) Insert(r), ans--;
    int q=Succ();
    if(!p && !q)
    {
        ans+=T[rt].size;
        rt=0;
        return;
    }
    if(!p)
    {
        ans+=T[T[rt].son[0]].size;
        T[rt].son[0]=0;
        PushUp(rt);
        return;
    }
    if(!q)
    {
        Splay(p, 0);
        ans+=T[T[rt].son[1]].size;
        T[rt].son[1]=0;
        PushUp(rt);
        return;
    }
    Splay(p, q);
    if(T[p].son[1])
        ans+=T[T[p].son[1]].size;
    T[p].son[1]=0;
    PushUp(p);
    PushUp(q);
}

int N, MIN, k;
char ch;
int main()
{
    scanf("%d %d", &N, &MIN);
    while(N--)
    {
        scanf(" %c %d", &ch, &k);
        switch (ch)
        {
            case 'I':
                if(k >= MIN)
                    Insert(k);
                break;
            case 'A':
                T[rt].key+=k;
                Add[rt]+=k;
                break;
            case 'S':
                T[rt].key-=k;
                Add[rt]-=k;
                Delete(-INF, MIN-1);
                break;
            case 'F':
                if(T[rt].size-k+1>0)
                    printf("%d\n", T[GetPth(T[rt].size-k+1, 0)].key);
                else
                    printf("-1\n");
        }
    }
    printf("%d\n", ans);
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/Mathics/p/3978047.html

# P1486 [NOI2004] 郁闷出纳员 ## 题目描述 OIER 公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的工资。这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好,就可能把每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把当前在公司的所有员工的工资扣除一个相同的量。我真不知道除了调工资他还做什么其它事情。 工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。每位员工的工资下界都是统一规定的。每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘了一位新员工,我就得为他新建一个工资档案。 老板经常到我这边来询问工资情况,他并不问具体某位员工的工资情况,而是问现在工资第 $k$ 多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后告诉他答案。 好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样,不是很困难吧? 如果某个员工的初始工资低于最低工资标准,那么将不计入最后的答案内。 ## 输入格式 第一行有两个整数 $n$ 和 $\min$。$n$ 表示下面有多少条命令,$\min$ 表示工资下界。 接下来的 $n$ 行,每行一个字符 $x$ 和一个整数 $k$,表示一条命令。命令可以是以下四种之一: - `I k` 新建一个工资档案,初始工资为 $k$。如果某员工的初始工资低于工资下界,他将立刻离开公司。 - `A k` 把每位员工的工资加上 $k$。 - `S k` 把每位员工的工资扣除 $k$。 - `F k` 查询第 $k$ 多的工资。 在初始时,可以认为公司里一个员工也没有。 ## 输出格式 对于每条 `F` 命令,你的程序要输出一行,仅包含一个整数,为当前工资第 $k$ 多的员工所拿的工资数,如果 $k$ 大于目前员工的数目,则输出 $-1$。 输出的最后一行包含一个整数,为离开公司的员工的总数。 请注意,初始工资低于工资下界的员工不算做离开公司的员工。
05-03
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值