【平衡二叉樹】【NOI2004】郁闷的出纳员

【问题描述】
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

第一個Size Balanced Tree(SBT)程序。

此程序中包含SBT的插入,刪除(非標準),查找第k大等操作。

使用偽鏈表的方式維護各個SBT,詳細細節見程序。

Accode:

#include <cstdio>
#include <cstdlib>

const char fi[] = "cashier.in";
const char fo[] = "cashier.out";
const int maxN = 100010;

int sz[maxN], key[maxN];
int lc[maxN], rc[maxN];
int N, Lim, T, tot, ans, delta;

  void init_file()
  {
    freopen(fi, "r", stdin);
    freopen(fo, "w", stdout);
  }

  inline void R_rotate(int &T)
  {
    int tmp = lc[T];
    lc[T] = rc[tmp];
    rc[tmp] = T;
    sz[tmp] = sz[T];
    sz[T] = sz[lc[T]] + sz[rc[T]] + 1;
    T = tmp;
  } //右旋。

  inline void L_rotate(int &T)
  {
    int tmp = rc[T];
    rc[T] = lc[tmp];
    lc[tmp] = T;
    sz[tmp] = sz[T];
    sz[T] = sz[lc[T]] + sz[rc[T]] + 1;
    T = tmp;
  } //左旋。

  inline void Maintain(int &T, bool flag)
  {
    if (!flag)
    {
      if (sz[lc[lc[T]]] > sz[rc[T]])
        R_rotate(T);
      else
        if (sz[rc[lc[T]]] > sz[rc[T]])
        {
          L_rotate(lc[T]);
          R_rotate(T);
        }
        else return;
    }
    else
    {
      if (sz[rc[rc[T]]] > sz[lc[T]])
        L_rotate(T);
      else
        if (sz[lc[rc[T]]] > sz[lc[T]])
        {
          R_rotate(rc[T]);
          L_rotate(T);
        }
        else return;
    }
    Maintain(lc[T], false);
    Maintain(rc[T], true);
    Maintain(T, false);
    Maintain(T, true);
  }	//若flag為false,則調整因左子樹
	//的插入而引起的不平衡;
	//否則調整因右子樹的插入而引起的不平衡。

  inline void insert(int &T, int v)
  {
    if (!T)
    {
      key[T = ++tot] = v;
      lc[T] = rc[T] = 0;
      sz[T] = 1;
      return;
    }	//若該插入的子樹為空,
	//則新分配空間,並插入節點。
    ++sz[T];
    if (v < key[T]) insert(lc[T], v);
	//若要插入的節點比當前節點小,
	//則插入到左子樹。
    else insert(rc[T], v);
	//否則插入到右子樹。
    Maintain(T, v >= key[T]);
	//調整T使其成為SBT。
  }

  inline int Del(int &T)
  {
    if (!T) return 0;
    int sum = 0;
    if (key[T] + delta < Lim)
    {
      sum += sz[lc[T]] + 1;
	//先將該節點連通左子樹一併去除。
      sz[T] -= sum;
      lc[T] = 0;
	//左子樹置為空。
      int tmp = Del(rc[T]);
	//遞歸遍曆右子樹。
      sum += tmp;
      sz[T] -= tmp;
      sz[rc[T]] = sz[T];
	//T的大小標記向下傳。
      T = rc[T];
	//T指向右子樹。
      return sum;
    }	//若當前節點滿足刪除要求,
	//則將該節點連同左子樹一併去除,
	//並遞歸遍曆右子樹看是否有可以去除的節點。
    sum += Del(lc[T]);
	//遞歸遍曆左子樹。
    sz[T] -= sum;
    return sum;
  }	//按條件對T這顆SBT進行檢查,
	//遇到該去除的節點即去除,
	//並返回去除的節點數。

  inline int Select(int &T, int k)
  {
    if (k == sz[lc[T]] + 1) return key[T];
    if (k < sz[lc[T]] + 1)
      return Select(lc[T], k);
    if (k > sz[lc[T]] + 1)
      return Select(rc[T], k - sz[lc[T]] - 1);
  }

  void work()
  {
    scanf("%d%d", &N, &Lim);
    int x;
    delta = 0;
	//由於題目中涉及到將所有的工資同時提高和降低,
	//所以設有delta的標記,以便統計。
    ans = 0;
    for (; N; --N)
    {
      int x;
      switch (getchar(), getchar())
      {
        case 'I':
        {
          scanf("%d", &x);
          if (x >= Lim) insert(T, x - delta);
	//這裡退出的人不算在總退出人數中。
	//插入只需要維護x與delta的差值即可。
          break;
        }
        case 'A':
        {
          scanf("%d", &x);
          delta += x;
          break;
        }
        case 'S':
        {
          scanf("%d", &x);
          delta -= x;
          ans += Del(T);
	//檢查是否有出去的員工。
          break;
        }
        case 'F':
        {
          scanf("%d", &x);
          if (x > sz[T]) printf("-1\n");
	//排除非法情況。
          else printf("%d\n",
            Select(T, sz[T] - x + 1) + delta);
          break;
        }
      }
    }
    printf("%d", ans);
  }

int main()
{
  init_file();
  work();
  exit(0);
}

再贴一个用跳跃表实现的程序:

在跳跃表中,始终要注意保证最上方一条链为空,且这条空链在查找时也需要被遍历。

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>

const int maxL = 100;
const int MAX = 0x3f3f3f3f;
const int MIN = ~MAX;

struct Node
{
    int sum[maxL], lev, key, cnt;
	//sum[i]为第i层该节点与其右边相邻
	//节点之间的节点个数(区间左开右闭),
	//lev为该点的层数范围(其对应区间为[0, lev)),
	//key为该点的关键值,cnt为该点的频数。
    Node *next[maxL];
	//next[i]为第i层的后继结点。
    Node() {}
    Node(int key, int cnt, int lev):
        key(key), cnt(cnt), lev(lev)
    {
        memset(sum, 0, sizeof sum);
        memset(next, 0, sizeof next);
    }
};
struct SkipList
{
    Node *head, *tail; //跳跃表的首尾指针。
    int lev; //跳跃表的高度。
    SkipList(): lev(1)
    {
        head = new Node(MIN, 0, 1);
        tail = new Node(MAX, 0, 1);
       //初始化头和尾,头的权值为负无穷,
       //尾的权值为正无穷,高度都为1。
        head -> next[0] = tail;	//最底层的初始化。
    } //构造一个空的跳跃表。
};

SkipList skip;
Node *update[maxL];
	//记忆化查找标记:update[i]为上一次查找到第i层的点。
int totnum[maxL], delta, tot, Lim;
	//记忆化查找标记:totnum[i]为上一次查找到第i层与其
	//右边相邻的点之间的节点个数(区间为左开右闭)。

inline int getint()
{
    int res = 0; char tmp;
    while (!isdigit(tmp = getchar()));
    do res = (res << 3) + (res << 1) + tmp - '0';
    while (isdigit(tmp = getchar()));
    return res;
}

inline int Rand()
{
    int tmp = 1;
    while (rand() & 1)
    {
        ++tmp;
        if (tmp > skip.lev || tmp == maxL - 1)
            break;
    }
    return tmp;
} //随机化算法,使加入高度为lev的概率为2^(-lev)。

inline Node *Find(int x)
{
    Node *p = skip.head;
    for (int i = skip.lev - 1; i > -1; --i)
    {
        for (totnum[i] = 0; p -> next[i] ->
             key < x; p = p -> next[i])
            totnum[i] += p -> sum[i];
        update[i] = p;
    }
    return p;
} //在跳跃表中找到小于x的最大元素。

inline int Del()
{
    Find(Lim - delta);
    int cnt = 0;
    for (int i = 0; i < skip.lev; ++i)
    {
        skip.head -> next[i] = update[i] -> next[i];
        skip.head -> sum[i] = update[i] -> sum[i] - cnt;
        cnt += totnum[i];
        while (skip.lev > 1 && skip.head ->
               next[skip.head -> lev - 2] == skip.tail)
        {
            skip.head -> sum[--skip.lev] = 0;
            --(skip.head -> lev);
            --(skip.tail -> lev);
        } //删除多余的空链(注意必须保留一层空链在最高层)。
    }
    tot -= cnt;
    return cnt;
} //在跳跃表中删除所有工资低于最低限度的点。

inline int Select(int k)
{
    Node *p = skip.head;
    for (int i = skip.lev - 1; i > -1; --i)
    for (; p != skip.tail && k > 0 &&
         p -> sum[i] < k; p = p -> next[i])
        k -= p -> sum[i];
    return p -> next[0] -> key;
} //在跳跃表中找出第k小的点。

inline void Ins(int x)
{
    Node *p = Find(x);
    if (p -> next[0] -> key == x)
    {
        p = p -> next[0]; ++(p -> cnt);
        for (int i = 0; i < skip.lev; ++i)
            ++(update[i] -> sum[i]);
        return;
    }
	//若x在跳跃表中已经存在,则直接将
	//该竖列的元素数目加一,否则插入新链。
    Node *t = new Node(x, 1, Rand());
    for (int i = skip.lev; i < t -> lev + 1; ++i)
    {
        update[i] = skip.head;
        update[i] -> sum[i] = tot;
        update[i] -> next[i] = skip.tail;
        totnum[i] = 0;
    }
	//若被插入的高度大于当前跳跃表
	//的高度,则在上方插入新链。
    if (t -> lev >= skip.lev)
        skip.head -> lev = skip.tail -> lev
            = skip.lev = t -> lev + 1;
    int cnt = 0;
    for (int i = 0; i < t -> lev; ++i)
    {
        t -> sum[i] = update[i] -> sum[i] - cnt;
        t -> next[i] = update[i] -> next[i];
        update[i] -> next[i] = t;
        update[i] -> sum[i] = cnt + t -> cnt;
        cnt += totnum[i];
    } //依次插入新链。
    for (int i = t -> lev; i < skip.lev; ++i)
        update[i] -> sum[i] += t -> cnt;
	//超过被插入的高度则直接加上该点权值即可。
    return;
}

int main()
{
    freopen("cashier.in", "r", stdin);
    freopen("cashier.out", "w", stdout);
    srand(time(NULL));
    int t = getint(), ans = 0, x; Lim = getint();
    for (; t; --t)
    switch (scanf("\n"), getchar())
    {
    case 'I':
        if ((x = getint()) >= Lim)
        {Ins(x - delta); ++tot;}
        break;
    case 'A': delta += getint(); break;
    case 'S':
        delta -= getint();
        ans += Del();
        break;
    case 'F':
        printf("%d\n", (x = tot - getint() + 1)
               > 0 ? (Select(x) + delta) : -1);
        break;
    }
    printf("%d\n", ans);
    return 0;
}

Splay版本:

/******************************\
 * @prob: NOI2004 cashier     *
 * @auth: Wang Junji          *
 * @stat: Accepted.           *
 * @date: June. 9th, 2012     *
 * @memo: 伸展树              *
\******************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>

const int maxN = 100010;

class SplayTree
{
private:
    struct Node
    {
        int key, sz, cnt; Node *lc, *rc, *F; Node() {}
        Node(int key): key(key), sz(1), cnt(1) {}
    } NIL[maxN], *tot, *T; int delta, Lim;
    Node *NewNode(int key)
    {
        Node *T = new (++tot) Node(key);
        T -> lc = T -> rc = T -> F = NIL;
        return T;
    }
    void update(Node *T) {T -> sz = T -> lc -> sz + T -> rc -> sz + T -> cnt; return;}
    void Zig(Node *T)
    {
        Node *P = T -> F, *tmp = T -> rc;
        if (P == this -> T) this -> T = T;
        else (P -> F -> lc == P) ? (P -> F -> lc = T) : (P -> F -> rc = T);
        T -> F = P -> F; P -> F = T; P -> lc = tmp;
        tmp -> F = T -> rc = P; update(P); return;
    }
    void Zag(Node *T)
    {
        Node *P = T -> F, *tmp = T -> lc;
        if (P == this -> T) this -> T = T;
        else (P -> F -> lc == P) ? (P -> F -> lc = T) : (P -> F -> rc = T);
        T -> F = P -> F; P -> F = T; P -> rc = tmp;
        tmp -> F = T -> lc = P; update(P); return;
    }
    void Splay(Node *&T, Node *t)
    {
        while (t != T)
        {
            Node *&P = t -> F;
            if (P == T) (P -> lc == t) ? Zig(t) : Zag(t);
            else
            {
                if (P -> F -> lc == P) (P -> lc == t) ? Zig(P) : Zag(t), Zig(t);
                else (P -> lc == t) ? Zig(t) : Zag(P), Zag(t);
            }
        }
        update(t); return;
    }
    Node *Ins(Node *&T, int v)
    {
        if (T == NIL) return T = NewNode(v); Node *tmp; ++T -> sz;
        if (v == T -> key) {++T -> cnt; return T;}
        if (v < T -> key) tmp = Ins(T -> lc, v), T -> lc -> F = T;
        else tmp = Ins(T -> rc, v), T -> rc -> F = T;
        return tmp;
    }
    void K_th(Node *&T, int k)
    {
        for (Node *t = T; t != NIL;)
        {
            if (k <= t -> lc -> sz + t -> cnt && k > t -> lc -> sz) {Splay(T, t); return;}
            if (k <= t -> lc -> sz) t = t -> lc;
            else k -= t -> lc -> sz + t -> cnt, t = t -> rc;
        }
        return;
    }
    void Find(Node *&T, int v)
    {
        for (Node *t = T; t != NIL;)
        {
            if (v == t -> key ||
                (v < t -> key && t -> lc == NIL) ||
                (v > t -> key && t -> rc == NIL))
            {Splay(T, t); return;}
            t = (v < t -> key) ? t -> lc : t -> rc;
        }
        return;
    }
    int Del()
    {
        int res = 0; if (T == NIL) return res; Find(T, Lim - delta);
        if (T -> key < Lim - delta)
            res = T -> lc -> sz + T -> cnt, T = T -> rc, T -> F = NIL;
        else T -> sz -= res = T -> lc -> sz, T -> lc = NIL;
        K_th(T, (T -> sz) >> 1); return res;
    }
public:
    SplayTree(): delta(0)
    {NIL[0].sz = 0; T = tot = NIL[0].lc = NIL[0].rc = NIL[0].F = NIL;}
    void set(int Lim) {this -> Lim = Lim; return;}
    int Add(int val) {delta += val; return (val < 0) ? Del() : 0;}
    void Ins(int v) {if (v >= Lim) Splay(T, Ins(T, v - delta)); return;}
    int K_th(int k)
    {
        if (k > T -> sz) return -1;
        K_th(T, T -> sz - k + 1);
        return T -> key + delta;
    }
} Tr; char ch; int T, Lim, ans, k;

int main()
{
    freopen("cashier.in", "r", stdin);
    freopen("cashier.out", "w", stdout);
    scanf("%d%d", &T, &Lim); Tr.set(Lim);
    while (T--) switch (scanf("\n%c%d", &ch, &k), ch)
    {
    case 'I': Tr.Ins(k); break;
    case 'A': Tr.Add(k); break;
    case 'S': ans += Tr.Add(-k); break;
    case 'F': printf("%d\n", Tr.K_th(k)); break;
    }
    printf("%d\n", ans); return 0;
}

/*

注意每个结点都维护一个cnt,即相同的数的个数,否则删除的时候会出问题。

*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值