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