Acwing-4366. 上课睡觉

文章讨论了一道编程题目,涉及石子合并的游戏。给定一组石子的总数和每堆石子的初始数量,目标是通过合并相邻石子堆,使得最后所有堆的石子数相等。关键在于找到最小的合并次数。文章提到可以通过枚举石子堆的最终数量(sum的约数),并检查是否能通过合并操作达到该数量,其中检查过程采用从前向后扫描每一段石子的方法。最终,代码实现了一个解决方案,用于找出满足条件的最小操作次数。
摘要由CSDN通过智能技术生成

假设最终答案为每堆石子均为cnt个,cnt一定可以整除sum(石子的总数),我们可以依次枚举答案。sum小于等于10^6,所以cnt的数量等于sum约数的个数,10^6范围内,约数最多的数为720720,它的约数个数有240个,int范围内(2147483647),约数个数最多有1600个。

我们可以枚举每一个cnt的取值能否取到,然后在所有可以取到的范围里面,找到一个操作数量方案最少的一个,最后每一堆石子的个数是cnt个的话,则一共有sum / cnt 堆,初始的时候有n堆,每操作一次会减少一堆,所以我们最终的操作数量是n - (sum / cnt),我们希望这个数越小越好,所以应该让cnt越小越好,所以本题我们就是要找到一个最小的cnt(cnt为最终每一堆石子的数量),使得可以取到。所以,我们可以从小到大枚举cnt,看看成不成立就可以了。

本题一定是有解的,最坏情况下,cnt取到sum,n堆石子合并为1堆。

接下来,思考一下如何判断cnt是否成立?(即判断最后能否将每堆石子的数量变成cnt)

本题每次石子合并只能合并相邻两堆,最后每一堆在原始序列中应该是连续的一段,此时问题等价于能否将序列分成若干段,使得每一段中所有数的和是cnt

 那如何判断能否将序列分成若干段,使得每一段中所有数的和是cnt呢?

我们可以从前往后考虑,先看第一段,第一段我们从前往后枚举,逐步将每一堆加到第一段里,在枚举的过程中我们维护一下当前的总和是多少,当当前总和s < cnt时,意味着当前总和不够cnt,就必须再添加新的元素,直到第一段的总和>=cnt为止

如果加完每一堆后,石子总数>cnt了,那就表示一定无解,因为去掉一堆则小于cnt,一定不够,加一堆则大于cnt,这就意味着第一段的总和一定不可能是cnt。

如果当前石子总和=cnt,下一堆是0,可以加上,下一堆不是0,就不能加了,如果发现当前段石子的总和=cnt了,就可以去枚举下一段,依次去计算每一段能不能凑出来cnt,如果全部能凑出来的,表明cnt是合法的,否则表示cnt不合法。

判断cnt是否合法,从前往后扫描每一段,只要当前这一段总和小于cnt就一直加,直到加到>=cnt为止,如果大于cnt就无解,如果等于cnt则表示这一段枚举完了,再继续枚举下一段,每一次判断cnt是否成立需要O(n)次,我们一共最多有240个约数备选,所以计算量总共是240*n。

我们可以枚举最后每一堆的个数,也可以枚举堆数,最后的堆数越多,则减少的堆数就越少,操作数量就越少,每一堆的数量是总和的约数,那么堆数也是总和的约数,因为每一堆的数量*堆数=总和

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010;

int w[N];
int n;

bool check(int k)
{
    int s = 0;
    for (int i = 0; i < n; ++ i)
    {
        s = s + w[i];
        if (s > k) return false;
        if (s == k) s = 0;
    }
    return true;
}

int main()
{
    int t;
    scanf("%d", &t);
    
    while (t --)
    {
        scanf("%d", &n);
        
        int sum = 0;
        for (int i = 0; i < n; ++ i)
        {
            scanf("%d", &w[i]);
            sum += w[i];
        }
        
        for (int i = n; i > 0; -- i)
        {
            if (sum % i == 0 && check(sum / i))
            {
                printf("%d\n", n - i);
                break;
            }
        }
    }
    
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青衫客36

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值