跳跃表解决郁闷的出纳员问题

一 问题描述

有个郁闷的出纳员,他负责统计员工的工资,但老板经常把每位员工的工资都加上或扣除一个相同的量。一旦某位员工发现工资低于工资下限,他就会立刻辞职。每位员工的工资下限都是统一规定的,每次有员工辞职,都要删去其工资档案;每次招聘一位新员工,都要为其新建一个工资档案。老板经常询问现在工

资第 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;
    }
}

八 测试

 

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值