【071】计算"组成字符串ku的最大次数" | 豆包MarsCode AI 刷题

79 阅读4分钟

前言

在解决问题的过程中,我们通常会面对时间和资源的权衡,如何以最优方式使用现有资源完成目标是计算机科学中一个常见的难题。今天,我们将探索一个趣味问题——蛋糕工厂的产能规划,通过动态规划结合二分查找的方式来求解,最终得出生产指定数量蛋糕所需的最少天数。

题目

image.png

问题分析

蛋糕工厂的产能规划问题可以归类为资源分配与优化问题,其核心目标是以最少的天数生产出足够的蛋糕。题目不仅涉及生产过程,还涉及资源的动态调配,具有以下几个关键要点:

1. 初始条件
  • 工厂从 mmm 台机器和 www 个工人开始,每天的生产量为 m×wm \times wm×w。
  • 每台机器或工人的购买成本为 ppp 个蛋糕。
  • 目标是尽快生产 nnn 个蛋糕。
2. 操作限制
  • 每天可以选择:

    1. 使用现有的机器和工人生产蛋糕。
    2. 使用生产出的蛋糕购买新的机器或工人。
    3. 直接存储蛋糕,用于后续的购买或目标完成。
3. 问题特点
  • 动态变化的生产力:购买机器或工人后,生产能力 m×wm \times wm×w 会实时增长。
  • 资源的有限性:蛋糕的数量决定了购买的可能性,购买后蛋糕会减少。
  • 目标驱动的优化:每天的选择需要围绕最小化生产天数来决策。

思路

为了有效解决这一问题,需要结合二分查找动态模拟两种方法。


二分查找(优化外层循环)

利用二分查找锁定完成目标的最小天数范围:

  1. 初始天数范围为 [1,1018][1, 10^{18}][1,1018],因为最坏情况下,每天只能生产一个蛋糕。
  2. 中间值 midmidmid 表示候选的天数。
  3. 判断在 midmidmid 天内是否可以生产出至少 nnn 个蛋糕。

动态模拟(验证内层逻辑)

在给定天数 midmidmid 内,模拟每日的生产和购买过程:

  1. 直接生产判断:如果当前的生产能力 m×wm \times wm×w 在剩余天数内可以生产出足够的蛋糕,直接返回成功。

  2. 等待购买:如果蛋糕不足以购买机器或工人,则计算需要等待的天数,更新蛋糕数量。

  3. 购买策略

    • 优先平衡机器和工人数量:使 mmm 和 www 越接近,生产效率越高。
    • 利用当前蛋糕数尽可能多地购买机器和工人。
  4. 逐日更新:每天模拟生产和购买后,更新蛋糕数和 m,wm, wm,w 的数量。

代码实现

以下是具体实现的代码(基于Java语言)

import java.math.BigInteger;

public class Main {

    public static int solution(int m, int w, int p, int n) {
        BigInteger machines = BigInteger.valueOf(m);
        BigInteger workers = BigInteger.valueOf(w);
        BigInteger price = BigInteger.valueOf(p);
        BigInteger goal = BigInteger.valueOf(n);

        BigInteger low = BigInteger.ONE;
        BigInteger high = BigInteger.valueOf(1_000_000_000_000_000_000L);
        BigInteger ans = high;

        while (low.compareTo(high) <= 0) {
            BigInteger mid = low.add(high).divide(BigInteger.valueOf(2));

            if (possible(mid, machines, workers, price, goal)) {
                ans = mid;
                high = mid.subtract(BigInteger.ONE);
            } else {
                low = mid.add(BigInteger.ONE);
            }
        }

        return ans.intValue(); // 将结果转换为 int 类型
    }

    private static boolean possible(BigInteger mid, BigInteger machines, BigInteger workers, BigInteger price, BigInteger goal) {
        BigInteger day = BigInteger.ZERO;
        BigInteger candies = BigInteger.ZERO;

        while (day.compareTo(mid) < 0) {
            BigInteger remainingDays = mid.subtract(day);
            BigInteger maxCandiesPossible = candies.add(machines.multiply(workers).multiply(remainingDays));
            if (maxCandiesPossible.compareTo(goal) >= 0) {
                return true;
            }

            if (candies.compareTo(price) < 0) {
                BigInteger required = price.subtract(candies);
                BigInteger productionPerDay = machines.multiply(workers);
                BigInteger daysToWait = required.add(productionPerDay).subtract(BigInteger.ONE).divide(productionPerDay);

                if (day.add(daysToWait).compareTo(mid) >= 0) {
                    return false;
                }

                candies = candies.add(daysToWait.multiply(productionPerDay));
                day = day.add(daysToWait);
            }

            // 买机器或工人
            BigInteger numBuy = candies.divide(price);
            candies = candies.subtract(numBuy.multiply(price));

            // 平衡机器和工人
            if (machines.compareTo(workers) < 0) {
                BigInteger diff = workers.subtract(machines);
                BigInteger toAdd = diff.min(numBuy);
                machines = machines.add(toAdd);
                numBuy = numBuy.subtract(toAdd);
            } else {
                BigInteger diff = machines.subtract(workers);
                BigInteger toAdd = diff.min(numBuy);
                workers = workers.add(toAdd);
                numBuy = numBuy.subtract(toAdd);
            }

            // 分配剩余购买
            BigInteger half = numBuy.divide(BigInteger.valueOf(2));
            machines = machines.add(half);
            workers = workers.add(numBuy.subtract(half));

            candies = candies.add(machines.multiply(workers));
            day = day.add(BigInteger.ONE);

            if (candies.compareTo(goal) >= 0) {
                return true;
            }
        }

        return candies.compareTo(goal) >= 0;
    }

    public static void main(String[] args) {
        System.out.println(solution(3, 1, 2, 12) == 3);
        System.out.println(solution(10, 5, 30, 500) == 8);
        System.out.println(solution(3, 5, 30, 320) == 14);
    }
}

代码解析

  1. 二分查找: 通过调整天数范围快速逼近最优解,时间复杂度为 O(log⁡(max_days)×T)O(\log(max_days) \times T)O(log(max_days)×T),其中 TTT 是每天的模拟时间。
  2. 动态模拟: 模拟每日生产与资源分配的过程,确保在给定天数内尽量高效地完成目标。
  3. 边界处理: 使用 BigInteger 处理大数计算,避免溢出问题。

复杂性分析

  1. 时间复杂度

    • 二分查找的复杂度为 O(log⁡(max_days))O(\log(max_days))O(log(max_days)),其中 max_daysmax_daysmax_days 为 101810^{18}1018。
    • 每次模拟的复杂度为 O(T)O(T)O(T),其中 TTT 是最多需要模拟的天数(通常远小于 midmidmid)。
    • 综合复杂度为 O(log⁡(max_days)×T)O(\log(max_days) \times T)O(log(max_days)×T)。
  2. 空间复杂度

    • 主要存储变量 m,w,p,nm, w, p, nm,w,p,n,使用 BigInteger 处理大数计算,空间复杂度为 O(1)O(1)O(1)(除了常数倍开销)。

最后

这一问题结合了算法优化资源分配策略,展示了动态规划、贪心算法与二分查找的完美配合。在实际开发中,类似的场景如工厂生产优化、任务调度等,都可以借鉴这一思想,通过分步模拟与优化逐步逼近最优解。