石子游戏(前缀和加枚举)

原题链接
https://www.acwing.com/problem/content/3996/
题目描述

有 n 堆石子,石子数量分别为 a1,a2,…,an。

现在,需要你通过取石子操作,使得所有堆石子的数量都相同。

一轮取石子操作的具体流程为:

设定一个石子数量上限 h。
检查每堆石子,对于石子数量大于 h 的石子堆,取出多余石子,使其石子数量等于 h。
要求,在一轮取石子操作中取走的石子数量不得超过 k。

请计算并输出为了使得所有堆石子的数量都相同,最少需要进行多少轮取石子操作。

输入格式
第一行包含两个整数 n,k。

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

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

数据范围
前六个测试点满足, 1≤n≤k≤10。
所有测试点满足,1≤n≤2×105,n≤k≤109,1≤ai≤2×105。

输入样例1:
5 5
3 1 2 2 4
输出样例1:
2
输入样例2:
4 5
2 3 4 5
输出样例2:
2

ac代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int n, k, l, r, ans;
int ss[200010], s[200010];
int main(){
    cin >> n >> k;
    for (int i = 1, x ; i <= n ; i ++){
        scanf("%d", &x);
        s[x] ++;
        ss[x] += x;
        r = max(r, x);
    }

    l = r;
    for (int i = 1 ; i <= r ; i ++)
        s[i] += s[i - 1], ss[i] += ss[i - 1];

    // 这里的 l 即为 h,表示我们每次操作的下界,我们每次操作都会把比 l 大的数变为l
    // 而 r 即为当前序列中最大的数(也就是上一次操作的 h,对于比 h 大的数我们不需修改,直接忽略)
    while(s[l - 1] != 0){   //当没有比 l 更小的数的时候说明模拟结束
        while(ss[r] - ss[l] - (s[r] - s[l]) * l <= k && s[l] != 0) l --;
        // 注意这里s[l] != 0,如果 l + 1  已经是最小的数就不用再减小l了
        l ++;
        ans ++;
        ss[l] += (s[r] - s[l]) * l;
        s[l] += (s[r] - s[l]);
        // 更新 s[l] 和 ss[l]
        r = l;
    }
    cout << ans;
}

作者:Randolph
链接:https://www.acwing.com/solution/content/60381/
来源:AcWing

一位大佬的题解,非常巧妙的方法。

前缀和 + 巧妙暴力模拟QwQ

自己在比赛时创造的一种独特前缀和做法QwQ,虽然说这题可能有更简洁的做法,但是我觉得这个前缀和的新技巧还是挺有趣的

理解题意,可以思考这样一种暴力模拟的思路:

刚开始把 h 设为最大的数,然后将所有比 h 大的数都减去 h 得到的总和与 k 比较,如果不超过 k ,则可以将 h - -,贪心找到使总和不超过 k 的最小的 h ,然后将比 h 大的数都变为 h ,重复模拟该过程直到所有数相同(即 h 取到最小的数)

关键的难点就在于如何快速求出这个总和,我们灵机一动不难发现,用前缀和可以解决:

s[i] 表示数字 i 出现的次数,再开一个 ss[i] 数组,数字 i 每出现一次,ss[i] 就加上 i ,然后对 s[i] 和 ss[i] 数组求一遍前缀和。

这样一来,用ss[r] - ss[l]可以求出在 (l, r] 区间内所有数的和,用 s[r] - s[l] 可以求出在 (l, r] 区间内所有数的个数,

那么你就会惊奇地发现,我们所要求的总和即为 ss[r] - ss[l] - (s[r] - s[l]) * l

另外,暴力模拟中有一步操作是将比 h 大的数都变为 h,我们没必要真的一个一个去修改比 h 大的数,因为他们对 h 之前的前缀和并不会产生影响,我们只需对 s[h] 和 ss[h] 进行更新即可

从这我学到了:先通过暴力方法梳理清楚思路,再对每一步思考可行的优化,不断提高解法的效率,无疑是一种不错的解题策略
先考虑暴力怎么做,再思考怎么通过算法来优化

另外在做题时,发现一些问题。
int n, k, l, r, ans;
int ss[200010], s[200010]
定义变量时,如果都是局部变量,会超时。
如果单独定义数组为全局变量时,会Segmentation Fault;
至于为什么,我还不清楚。
不过以后写题定义变量最好是全局部,或者全全局,毕竟是一锤子买卖。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值