3993 石子游戏(贪心 + 前缀和)

1. 问题描述:

有 n 堆石子,石子数量分别为 a1,a2,…,an。现在,需要你通过取石子操作,使得所有堆石子的数量都相同。一轮取石子操作的具体流程为:
设定一个石子数量上限 h。
检查每堆石子,对于石子数量大于 h 的石子堆,取出多余石子,使其石子数量等于 h。
要求,在一轮取石子操作中取走的石子数量不得超过 k。
请计算并输出为了使得所有堆石子的数量都相同,最少需要进行多少轮取石子操作。

输入格式

第一行包含两个整数 n,k。第二行包含 n 个整数 a1,a2,…,an。

输出格式

一个整数,表示所需的最少取石子操作轮次。

数据范围

前六个测试点满足, 1 ≤ n ≤ k ≤ 10。
所有测试点满足,1 ≤ n ≤ 2 × 10 ^ 5,n ≤ k ≤ 10 ^ 9,1 ≤ ai ≤ 2 × 10 ^ 5。

输入样例1:

5 5
3 1 2 2 4

输出样例1:

2

输入样例2:

4 5
2 3 4 5

输出样例2:

2
来源:https://www.acwing.com/problem/content/description/3996/

2. 思路分析:

分析题目可以知道ai最大是2 * 10 ^ 5,这就意味着我们可以使用枚举来解决,对于一道题目来说我们要看的是题目的数据范围,根据数据范围大概确定有哪些方法,然后再考虑怎么做,对于这道题目我们可以先画画图:

我们可以将n堆石子看成是一根根对应高度的柱子,当前柱子的最高高度为maxh,我们需要找到一个下界使得在满足不超过k的情况下拿走更多的石子,也即下界需要尽可能小这样可以使得拿走的石子更多对应的操作次数就越少,我们可以从j = maxh开始枚举,判断[j,maxh]石子的的数量是否超过了k,如果没有超过说明拿走,所以可以使用循环找到下界j,所以问题就转化为了如何快速求解某个区间的[j,maxh]的柱子的数量,可以发现我们可以使用前缀和进行维护,我们可以将一开始输入的石子数量看成是高度,将其插入到高度数组h的对应位置上,然后将前缀和计算到h中,h[i]表示高度为[1,h]之间的柱子的数量,这样我们就可以使用区间和快速求解出高度在[i,j]之间的柱子数量,从当前的柱子的最高高度开始枚举,每一次加上上界与当前位置的前缀和判断是否在小于等于k,如果满足则继续往下,相当于线是一直往下移动的最终到minh的位置为止,minh为石子数量的最小值。

3. 代码如下:

from typing import List

class Solution:
    # 求解高度为[j, maxh]有多少个柱子
    def get(self, j: int, maxh: int, h: List[int]):
        return h[maxh] - h[j - 1]

    def process(self):
        n, m = map(int, input().split())
        a = list(map(int, input().split()))
        N = 200010
        h = [0] * N
        minh, maxh = N, 0
        for x in a:
            h[x] += 1
            # 求解当前的最小高度与最高高度
            minh = min(minh, x)
            maxh = max(maxh, x)
        # 将前缀和累加到h上, 这样h[i]存储的是高度为[1, i]的数量
        for i in range(1, maxh + 1):
            h[i] += h[i - 1]
        # 从最高的高度开始枚举
        i = maxh
        res = 0
        # 一直枚举到最小高度为止, i相当于是第一条分割线, j是第二条分割线, 区间[i, j]之间的部分都是可以去除的
        while i > minh:
            j, s = i, 0
            while j > minh and s + self.get(j, maxh, h) <= m:
                s += self.get(j, maxh, h)
                j -= 1
            i = j
            res += 1
        return res


if __name__ == '__main__':
    print(Solution().process())
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值