Skip to content

L. Bridge Renovation - 题解

比赛与标签

比赛: 2024-2025 ICPC, NERC, Southern and Volga Russian Regional Contest (Unrated, Online Mirror, ICPC Rules, Preferably Teams) 标签: brute force, dp, greedy, math, two pointers 难度: *1400

题目大意喵~

主人,你好喵~!这道题是说,我们要帮公园主管 Monocarp 计算最少需要买多少根 60 单元长的标准木板,来装修三座桥的说。

这三座桥分别需要 n 根 18 单元、n 根 21 单元和 n 根 25 单元的木板。我们可以把标准木板切成任意小段,但不能拼接起来哦。我们的任务就是,用最少的标准木板,满足所有需求,喵~

简单来说:

  • 输入: 一个整数 n,表示每种规格(18、21、25)的木板都需要 n 根。
  • 输出: 满足所有需求所需的最少的 60 单元标准木板数量。

解题思路,喵呜~

这道题看起来像一个经典的“装箱问题”(Bin Packing Problem),我们要把指定尺寸的“物品”(18, 21, 25)装进容量固定的“箱子”(60)里,目标是用的箱子最少,喵。

直接模拟所有切割方案太复杂了,肯定会超时的说。所以,我们得找找其中的规律!

首先,我们来想想怎么切割才最划算。最划算当然是浪费最少的空间啦!

  • 一根 60 单元的木板
  • 需要的木板尺寸:18, 21, 25

我们来试试组合一下:

  • 25 + 25 = 50 (浪费 10)
  • 21 + 21 + 18 = 60 (哇!零浪费!太完美了喵!)
  • 18 + 18 + 18 = 54 (浪费 6)

21 + 21 + 18 这个组合简直是天赐的礼物,一点儿都不浪费!但是,它提供了两根 21 和一根 18,和我们 n:n:n 的需求比例不完全匹配,所以我们不能只用这一种切法。

那我们换个角度!考虑获得一整“套”木板(一根18、一根21、一根25)需要多少材料。 总长度是 18 + 21 + 25 = 64 单元。 我们的标准木板只有 60 单元长。这意味着,每获得一整套,我们都“欠下”了 64 - 60 = 4 单元的长度。这个“债务”必须由更多的标准木板来偿还。

这个思路虽然直观,但要精确计算何时需要额外的木板还是有点复杂。不如我们从小 n 开始,手动推算一下,找找规律吧!

  • 当 n = 1: 需要 (18, 21, 25) 各一根。总长 64。
    • 一根60单元木板肯定不够。
    • 两根呢?2 * 60 = 120。空间足够。可以这样切:
      • 木板1: 切一个 25,再切一个 21 ( 25+21=46,剩下14 )
      • 木板2: 切一个 18 ( 18,剩下42 )
    • 所以 n=1 时,需要 2 根。
  • 当 n = 2: 需要 (18, 21, 25) 各两根。总长 128。128 / 60 ≈ 2.13,所以至少需要3根。可以证明3根是够的。
  • 当 n = 3: 需要 (18, 21, 25) 各三根。总长 192。192 / 60 = 3.2,所以至少需要4根。

我们把答案列出来看看:

  • A(1) = 2
  • A(2) = 3
  • A(3) = 4
  • A(4) = 5
  • A(5) = 6
  • A(6) = 7

喵~ 看起来答案好像就是 n + 1 嘛!但是别急,我们来算算 n=7 的情况。 经过一番复杂的计算(或者可以相信样例解释的思路),会发现 n=7 时需要 9 根木板!

啊哈!规律好像断了一下!我们来看看答案的增量:

  • A(1) - A(0) = 2 (假设 A(0)=0)
  • A(2) - A(1) = 1
  • A(3) - A(2) = 1
  • A(4) - A(3) = 1
  • A(5) - A(4) = 1
  • A(6) - A(5) = 1
  • A(7) - A(6) = 9 - 7 = 2

发现了吗?每当 n 增加 1,通常只需要多加 1 根木板。但是,在某些特殊时刻,需要多加 2 根!这个特殊时刻发生在 n=1n=71, 7, 13, 19, ... 这些数字有什么共同点呢?它们都是 6k + 1 的形式,也就是 n % 6 == 1 的说!

所以我们找到了一个美妙的递推关系:

  • A(0) = 0
  • n % 6 == 1 时, A(n) = A(n-1) + 2
  • 否则, A(n) = A(n-1) + 1

根据这个关系,总木板数 A(n) 就是 n+1 的基础,再加上每次 +2 时多出来的那个 +1。 多出来的 +1 发生在 n = 1, 7, 13, ... 的时候。 那么对于给定的 n,我们需要数一下从 1 到 n 有多少个数满足 i % 6 == 1。 这个数量正好是 (n-1)/6 + 1(在整数除法下)。

所以,最终的公式就是: 总数 = n (基础增加) + 额外增加的次数A(n) = n + ((n - 1) / 6 + 1)

这个公式简洁又优雅,完美地解决了问题,喵~!

代码实现喵

cpp
#include <iostream>

int main() {
    // 使用快速I/O,让程序跑得像猫一样快,喵~
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int n;
    std::cin >> n;

    // 这个问题是求解需要多少根60单位的木板,来获得n根18、n根21和n根25的木板。
    // 这是一个打包问题的变种。由于物品尺寸固定且每种数量相同,我们可以分析最优策略。
    //
    // 贪心思路是优先使用最高效的组合,即浪费最少的组合。
    // 组合 (21, 21, 18) 的总长为60,是完美的零浪费方案。
    // 尽可能多地使用这种组合总是最优的。
    //
    // 基于这个贪心策略进行详细的案例分析,会揭示一个关于总木板数 A(n) 的规律。
    // A(1) = 2
    // A(2) = 3
    // A(3) = 4
    // A(4) = 5
    // A(5) = 6
    // A(6) = 7
    // A(7) = 9
    //
    // 差分 A(n) - A(n-1) 在大多数情况下是 1,但当 n % 6 == 1 (n > 0) 时是 2。
    // 这给出了一个递推关系:
    // A(0) = 0
    // A(n) = A(n-1) + 2, 如果 n % 6 == 1
    // A(n) = A(n-1) + 1, 其他情况
    //
    // 这个递推关系的解是 A(n) = n + (在[1, n]中满足 m % 6 == 1 的 m 的数量)。
    // 这个数量等于 floor((n-1)/6) + 1。
    // 所以,封闭形式的解是 A(n) = n + floor((n-1)/6) + 1。
    //
    // 对于 n >= 1, C++的整数除法 (n - 1) / 6 等价于 floor((n - 1) / 6)。
    int result = n + (n - 1) / 6 + 1;

    std::cout << result << std::endl;

    return 0;
}

复杂度分析

  • 时间复杂度: O(1) 的说 我们只需要读入一个 n,然后用一个神奇的公式算一下就好了,完全不需要循环,所以超级快!是常数时间复杂度 O(1) 的说!

  • 空间复杂度: O(1) 的说 我们只用了几个变量来存 n 和结果,没有用数组之类的东西,所以空间也是常数 O(1) 喵~

知识点与总结

这道题真有趣,不是吗?它教会了我们:

  1. 核心思想: 观察规律 + 数学建模。这道题表面上看像个复杂的打包问题(Bin Packing),但实际上是披着贪心外衣的数学规律题,喵~ 与其陷入复杂的组合方案,不如退一步,从宏观的规律入手。

  2. 解题技巧:

    • 从小数据入手:当没有头绪时,手动模拟 n=1, 2, 3... 的情况是寻找突破口的绝佳方法。
    • 分析增量:观察 A(n)A(n-1) 之间的关系,是发现周期性规律的利器。
    • 规律公式化:一旦发现规律,就要尝试用一个简洁的数学公式来表达它。
  3. 注意事项:

    • 不要被贪心细节迷惑:虽然 21+21+18=60 是个很棒的组合,但围绕它构建完整的、最优的切割方案非常困难。有时候,最终结果的规律比过程要简单得多。
    • 理论下界不一定可达:我们计算过获得 n 套木板的总长度是 64n,所以至少需要 ceil(64n/60) 根木板。这个下界在 n=7 时是 8,但实际答案是 9。这说明由于木板不能拼接,存在无法避免的“打包浪费”,导致需要额外的木板。

所以呀,主人,遇到看起来很复杂的题目,不要害怕,先动动小爪子,从简单的例子开始分析,说不定就能发现藏在里面的小秘密哦!加油,你也可以的,喵~!

Released under the MIT License.