我报名参加金石计划 1 期挑战 —— 瓜分 10 万奖池,这是我的第 3 篇文章,点击查看活动详情。
题目来自 Open Kattis 上的 Annoyed Coworkers,难度为 3.2(中等)。
作者:Alex Lind
来源:2019 Virginia Tech High School Programming Contest (Problem H)
许可:
文章内容参考比赛主办方的题解大纲。
题目
又是在办公室的一天。你是一个不会独自完成任何工作的高管。相反,你会去找你的同事寻求“帮助”,伺机让他们完成所有的工作。
你很清楚知道,忙帮得越多,人就越恼火。在一开始时,同事对你的怒气值为 。当你想向他们寻求帮助时,他们的怒气程度会以恒定 的数量增加。
你希望在同事的“帮助”下完成 个任务项目,但你需要注意不要过度地惹恼任意一个同事。
那么,最好的策略是什么?
输入
第一行包含两个整数 和 ,其中 ()是你需要寻求帮助的次数,()表示的是你的同事数量。
接下来 行中的每一行包含两个正整数 和 ,代表每个同事对你的初始怒气值,和每次寻求帮助后的怒气增量 ()。
输出
输出一个数字,即同事对你的最大怒气值,前提是你应该最大限度地降低这个值。
例子
例 1
4 4
1 2
2 3
3 4
4 5
7
解释:
你有 个同事和 个项目需要“帮忙”。他们的初始怒气值为 ,,,,怒气增量为 ,,,。
其中一种最佳的解决方案是:向同事 请求帮忙两次,向同事 请求一次,向同事 请求一次。最终怒气值为:
对你最恼火的同事是同事 ,怒气值为 。
又或者,你可以向同事 请求帮忙三次,向同事 请求一次。最终怒气值为:
两种策略都产生相同的结果。
例 2
3 2
1 1000
1000 1
1002
例 3
5 2
1 1
2 2
5
题目要求
输入:,, 和
输出:最小化 的最大值
条件:()
解题思路
对于每次请求,我们每轮都应该选择请求后怒气值最小的那位同事。也就是说,其他的东西,像是初始值和增量什么的,都不是关键。
因此,我们需要频繁地查找最小值,但每次修改怒气值后都排序一遍显然是会超时的。
这时最小堆就派上用场了。
最小堆满足下列性质:
- 堆中某个结点的值总是不小于其父结点的值;
- 堆总是一棵完全二叉树。
根据这一性质,最小堆的根节点总是最小值。
在 Python 中,heapq 库提供了相关的算法。heapify 函数能将数组转换成堆,而 heappush 和 heappop 则分别对应插入和删除操作。更多详情请看这里。
注:堆的删除操作只能删除根节点。
时间复杂度:
空间复杂度:
代码
from heapq import heapify, heappop, heappush
# 请求数量,同事数量
h, c = map(int, input().split())
# 每个同事的 [怒气值,增量]
ai = [list(map(int, input().split())) for _ in range(c)]
# 预测请求后的怒气值
# `ai` 不需要了,`predicted` 可以直接覆盖它(这里就不这么做了)
predicted = [(a + d, d) for a, d in ai]
# 构建堆结构
heapify(predicted)
# 对于每个请求
for _ in range(h):
# 获取 `predicted` 中最小的值(也就是堆的根节点)
level, d = heappop(predicted)
# 请求同事 `idx` 的帮助
# 预测下一轮的请求后的怒气值
# `predicted` 不需要从新计算
# 只有一个同事的当前怒气值更改了,再为该同事的怒气值加个 `d` 即可
heappush(predicted, (level + d, d))
# 请求完毕
# 把 `predicted` 里的每个值回退一轮(减去 `d`),获得当前每个同事的怒气值
# 输出同事中的怒气最大值
print(max(level - d for level, d in predicted))
总结
- 对于最小化最大值/最大化最小值的问题,贪心算法可能适用。
- 需要多次查询列表中的最大值/最小值时,应考虑使用堆。
柿子挑软的捏。
相关知识点:贪心算法、堆(优先队列)