一 问题描述
有个郁闷的出纳员,他负责统计员工的工资,但老板经常把每位员工的工资都加上或扣除一个相同的量。一旦某位员工发现工资低于工资下限,他就会立刻辞职。每位员工的工资下限都是统一规定的,每次有员工辞职,都要删去其工资档案;每次招聘一位新员工,都要为其新建一个工资档案。老板经常询问现在工
资第 k 多的员工拿多少工资。
二 输入和输出
1 输入
第 1 行包含两个非负整数 n 和 min,n 表示命令的数量,min 表示工资下限。接下来输入 n 行,每行都表示一条命令。其中:I k 命令表示新建一个工资档案,初始工资为 k 。若某员工的初始工资低于工资下限,则他将立刻辞职;A k 命令表示把每位员工的工资都加上 k ;S k 命令表示把每位员工的工资都扣除 k ;F k 命令表示查询第 k 多的工资。开始时公司里一个员工也没有。数据范围:I 命令的条数不超过10^5 ;A 和 S 命令的总条数不超过 100;F 命令的条数不超过10^5 ;工资的每次调整量都不超过10^3 ;新员工的工资不超过10^5 。
2 输出
输出文件的行数为 F 命令的条数加 1。对每条 F 命令都输出一行,仅包含一个整数,为当前工资第 k 多的员工所拿的工资,若 k 大于当前员工的数量,则输出 -1。最后一行输出一个整数,为辞职的员工的总数。
三 输入和输出样例
1 输入样例
9 10
I 60
I 70
S 50
F 2
I 30
S 15
A 5
F 1
F 2
2 输出样例
10
20
-1
2
四 分析
本问题可以用跳跃表实现。
五 设计
(1)可以设置全局变量 add 记录增加的工资量,增加工资 k 时,直接 add+=k 即可。
(2)插入新员工的工资 k 时,若 k 大于或等于下限,则将 k-add 插入跳跃表中,员工总数 total++。
(3)扣除工资 k 时,add-=k ;在跳跃表中查询小于 MIN-add 的元素个数 sum,删除所有小于 MIN-add 的元素。辞职的员工的总数 ans+=sum,total-=sum。
(4)查询第 k 大的数时,若 k 大于 total,则输出 -1,否则查询第 total-k+1 小的数,加上 add 后输出。
本问题要求删除所有小于 MIN 的元素,因为跳跃表中的元素都是减 add 后存储的,因此删除所有小于 MIN-add 的元素即可。删除小于 val 的所有元素的步骤如下。
(1)通过查找,得到小于 val 的元素个数 sum,在查询过程中记录 tot[] 和 updata[]。
(2)将头节点的后继指针指向 updata[i] 的后继,这样就删除了所有小于 val 的元素。
(3)删除后需要更新 head->sum[i],如果有空链,则删除空链。
六 图解
删除表中小于 20 的所有元素,过程如下。
(1)首先查找小于20的元素个数。
(2)删除小于 20 的所有元素。头节点的后继指针跳过这些节点指向 updata[i] 的后继即可。
七 代码
package com.platform.modules.alg.alglib.p1486;
import java.util.Random;
public class P1486A {
public String output = "";
private int INF = 0x7fffffff;
private int MAX_LEVEL = 16;
int n, Min, ans, add, k, total; // 工资下限,离开公司员工数
public String cal(String input) {
Init();
String[] line = input.split("\n");
String[] num = line[0].split(" ");
n = Integer.parseInt(num[0]);
Min = Integer.parseInt(num[1]);
ans = add = total = 0;
for (int i = 0; i < n; i++) {
String[] command = line[i + 1].split(" ");
char ch = command[0].charAt(0);
k = Integer.parseInt(command[1]);
if (ch == 'I' && k >= Min) {
Insert(k - add);
total++;
} else if (ch == 'A') add += k;
else if (ch == 'S') {
add -= k;
int sum = Delete(Min - add);
ans += sum;
total -= sum;
} else if (ch == 'F') {
if (k > total) {
output += "-1\n";
} else {
output += String.format("%d\n", Get_kth(total - k + 1) + add);
}
}
}
output += String.format("%d\n", ans);
return output;
}
Node head;
Node updata[] = new Node[MAX_LEVEL];
int level;
int tot[] = new int[MAX_LEVEL];
class Node {
int val;
int sum[] = new int[MAX_LEVEL];
Node forward[] = new Node[MAX_LEVEL];
}
public P1486A() {
for (int i = 0; i < updata.length; i++) {
updata[i] = new Node();
}
}
void Init() {
level = 0;
head = new Node();
for (int i = 0; i < MAX_LEVEL; i++) {
head.forward[i] = null;
head.sum[i] = 0;
}
head.val = -INF;
}
// 按规则选择数据应该在哪层插入
int RandomLevel() {
int lay = 0;
while (new Random().nextInt(100) % 2 == 0 && lay < MAX_LEVEL - 1)
lay++;
return lay;
}
// 查找最接近val的元素
int Find(int val) {
Node p = head;
tot[level] = 0;
for (int i = level; i >= 0; i--) {
while (p.forward[i] != null && p.forward[i].val < val) {
tot[i] += p.sum[i];
p = p.forward[i];
}
if (i > 0)
tot[i - 1] = tot[i];
updata[i] = p; // 记录搜索过程中各级走过的最大节点位置
}
return tot[0]; // 返回小于val的数量
}
void Insert(int val) {
Node s;
int lay = RandomLevel();
if (lay > level) { // 要插入的层>现有层数
for (int i = level + 1; i <= lay; i++)
head.sum[i] = total;
level = lay;
}
Find(val); //查询
s = new Node();//创建一个新节点
s.val = val;
for (int i = 0; i < MAX_LEVEL; i++) {
s.forward[i] = null;
s.sum[i] = 0;
}
for (int i = 0; i <= lay; i++) { // 插入操作
s.forward[i] = updata[i].forward[i];
updata[i].forward[i] = s;
s.sum[i] = updata[i].sum[i] - (tot[0] - tot[i]);
updata[i].sum[i] -= s.sum[i] - 1;
}
for (int i = lay + 1; i <= level; i++)
updata[i].sum[i]++;
}
int Delete(int val) {
int sum = Find(val);
for (int i = 0; i <= level; i++) {
head.forward[i] = updata[i].forward[i];
head.sum[i] = updata[i].sum[i] - (tot[0] - tot[i]);
}
while (level > 0 && head.forward[level] == null) // 删除空链
level--;
return sum;
}
int Get_kth(int k) {
Node p = head;
for (int i = level; i >= 0; i--) {
while (p != null && p.sum[i] < k) {
k -= p.sum[i];
p = p.forward[i];
}
}
return p.forward[0].val;
}
}
八 测试