【问题描述】 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,詳細細節見程序。
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,即相同的数的个数,否则删除的时候会出问题。 */