洛谷 P1249 最大乘积 题解

原题链接

第一篇博客试水。

这是我几个月前做的一道黄题,当时洛谷上所有题解都只停留在粗浅地描述“感性”理解上,没有给出令人信服的严谨证明。于是,我花了半天时间,用更数学的语言写出这篇题解,希望能帮助到和我一样爱较真的同学。

Solution

\(n\) 的最佳分解方案是 \(n = a_1 + a_2 +\cdots + a_k\),其中 \(1\le a_1<a_2<\cdots<a_k\)。记 \(p = \prod_{i=1}^{k} a_i\) 为分解出自然数的最大乘积。

\(n \le 4\) 时,不难验证 \(n\) 的最佳分解方案就是不做分解,所以接下来我们只讨论 \(n \ge 5\) 时的情形。

首先,我们可以证明:

\(a_1\) 只可能是 \(2\)\(3\)\(4\)。【引理一】

证明 假设 \(a_1 \notin \{2,3,4\}\),对 \(a_1\) 分类讨论。

  • 如果 \(a_1 > 4\)\(a_1\) 是奇数,不妨设 \(a_1 = 2x+1, \ x\ge 2\),那么将 \(a_1\) 拆分为 \(x\)\(x+1\),此时 \(p\) 变为原来的 \(\dfrac{x(x+1)}{2x+1}\)倍,这个倍数大于 \(1\),与 \(p\) 取最大值矛盾。
  • 如果 \(a_1 > 4\)\(a_1\) 是偶数,不妨设 \(a_1 = 2x, \ x\ge 3\),那么将 \(a_1\) 拆分为 \(x-1\)\(x+1\),此时 \(p\) 变为原来的 \(\dfrac{(x-1)(x+1)}{2x}\)倍,这个倍数大于 \(1\),与 \(p\) 取最大值矛盾。
  • 如果 \(a_1 =1\),那么删去 \(a_1\) 并把 \(a_k\)\(1\),此时 \(p\) 变为原来的 \(\dfrac{a_{k}+1}{a_{k}}\) 倍,与 \(p\) 取最大值矛盾。

综上所述,假设不成立,原命题得证。\(\square\)

由于 \(a_1 < 5\)\(n \ge 5\)\(n\) 一定会被分解为两个及以上的数,即 \(k \ge 2\)。接下来,我们可以证明:

任意相邻 \(a_i\) 之差 \(\in\{1,2\}\),且至多存在一对相邻 \(a_i\) 满足差为 \(2\)。【引理二】

证明 假设上述命题不成立,则一定存在两个正整数 \(x,y\) 同时满足以下三点:

  • \(x,y \in [a_1,a_k]\)
  • \(x\)\(y\) 不是 \(\{a_i | i=1,2,\dots,k\}\) 中的数
  • 存在 \(i_1,i_2 \in \{1,2,\cdots,k\}\),使得 \(a_{i_1} = x-1, \ a_{i_2} = y+1\)

于是,将 \(a_{i_1}\) 换为 \(x\)\(a_{i_2}\) 换为 \(y\),此时 \(p\) 变为原来的 \(\dfrac{xy}{(x-1)(y+1)}\) 倍,这个倍数大于 \(1\),与 \(p\) 取最大值矛盾,故原命题得证。\(\square\)

这个命题极大帮助我们确定最优解,它表明最佳的分解方案一定是一个连续或近乎连续(至多有一点不连续)的数列,这与我们的直觉是一致的。它还进一步缩小了 \(a_1\) 的范围:

\(a_1 \ne 4\)。【引理三】

证明 假设 \(a_1=4\),由引理二,\(a_2=5\)\(6\)

  • 如果 \(a_2 = 5\),那么将 \(a_2\) 拆分为 \(2\)\(3\)\(p\) 变为原来的 \(\dfrac{6}{5}\) 倍,这与 \(p\) 取最大值矛盾。
  • 如果 \(a_2 = 6\),那么将 \(a_2\) 拆分为 \(2\)\(3\),并把 \(a_k\)\(1\)\(p\) 变为原来的 \(\dfrac{a_{k}+1}{a_{k}}\) 倍,这与 \(p\) 取最大值矛盾。

综上所述,假设不成立,故 \(a_1 \ne 4\)\(\square\)

目前,我们已经确定了最佳分解方案一定首项为 \(2\)\(3\),并且分解数列连续或近乎连续。通过下一条引理,我们可以继续缩小最优解范围:

如果 \(a_1 = 3\) 且存在一对相邻 \(a_i\) 满足差为 \(2\),那么这对相邻 \(a_i\) 必是 \(a_{k-1}\)\(a_k\)。【引理四】

证明 设这对相邻 \(a_i\)\(a_{t-1}\)\(a_{t}\)。假设 \(t < k\),将 \(a_{t}\)\(a_{t+1}\) 都自减 \(1\),并增添一项 \(2\),此时 \(p\) 变为原来的 \(\dfrac{2(a_{t}-1)(a_{t+1}-1)}{a_{t}a_{t+1}}\)倍,即 \(\dfrac{2(a_t-1)}{a_{t+1}}\) 倍,由于 $a_{t} > a_1 = 3 $,故这个倍数大于 \(1\),这与 \(p\) 取最大值矛盾,于是原命题得证。\(\square\)

根据上述四条引理,我们已经具备找到任意 \(n\) 的最佳分解方案的全部条件。

一方面,只有四种可能的最佳分解方案,分别是以 \(2\) 为首项的连续数列、以 \(2\) 为首项的近乎连续数列、以 \(3\) 为首项的连续数列和以 \(3\) 为首项的近乎连续数列。

另一方面,考虑一个给定的大于等于 \(5\) 的正整数 \(n\),可将其写成一个关于正整数 \(s\)\(r\) 的式子 \(n = r + \sum_{i=2}^{s} i\),其中 \(r\)\(s\) 都是唯一确定的且 \(0 \le r \le s\)。我们可以根据 \(r\) 的取值对 \(n\) 进行分类,其中每一类都对应一种最佳分解方案,具体而言:

\(r\) 的取值 最佳分解方案 具体形式
\(r = 0\) \(2\) 为首项的连续数列 \(2 + 3 + \cdots + s\)
\(1 \le r \le s-2\) \(2\) 为首项的近乎连续数列 \(2 + 3 + \cdots + (s-r) + (s-r+2) + (s-r+3) + \cdots+ (s+1)\)
\(r = s-1\) \(3\) 为首项的连续数列 \(3 + 4 + \cdots + (s+1)\)
\(r = s\) \(3\) 为首项的近乎连续数列 \(3 + 4 + \cdots + s + (s+2)\)

由于每一种分解方案对应的 \(n\)\(r\) 上也具有同样的共性,如以 \(2\) 为首项的连续数列对应的 \(n\)\(r\) 均为 \(0\),因此这种对应关系是唯一的。至此,我们找到了任意 \(n\) 的最佳分解条件,问题解决。

Code

#include <bits/stdc++.h>
using namespace std;

struct BigDec {
    int len, dec[10000];
    void operator*=(int x) {
        for (int i = 0; i < len; ++i)
            dec[i] *= x;
        int i = 0;
        for (int carry = 0; i < len || carry; ++i) {
            dec[i] += carry;
            carry = dec[i] / 10;
            dec[i] %= 10;
        }
        len = i;
    }
    void printDec() {
        for (int i = len - 1; i >= 0; --i)
            printf("%d", dec[i]);
    }
} p;

int main() {
    int n;
    scanf("%d", &n);
    if (n <= 4)
        return printf("%d\n%d", n, n) & 0;
    int s = (sqrt(8 * n + 9) - 1) / 2;
    int r = n - (s - 1) * (s + 2) / 2;
    p.dec[0] = p.len = 1;
    if (r == 0) {
        for (int i = 2; i <= s; ++i)
            printf("%d ", i), p *= i;
    } else if (r == s - 1) {
        for (int i = 3; i <= s + 1; ++i)
            printf("%d ", i), p *= i;
    } else if (r == s) {
        for (int i = 3; i <= s; ++i)
            printf("%d ", i), p *= i;
        printf("%d ", s + 2), p *= s + 2;
    } else {
        for (int i = 2; i <= s - r; ++i)
            printf("%d ", i), p *= i;
        for (int i = s - r + 2; i <= s + 1; ++i)
            printf("%d ", i), p *= i;
    }
    printf("\n");
    p.printDec();
    return 0;
}
posted @ 2026-03-30 22:05  SHUddol  阅读(14)  评论(0)    收藏  举报