题解:洛谷 P1249 最大乘积

【题目来源】

洛谷:P1249 最大乘积 - 洛谷(luogu.com.cn)

【题目描述】

一个正整数一般可以分为几个互不相同的自然数的和,如 \(3=1+2\)\(4=1+3\)\(5=1+4=2+3\)\(6=1+5=2+4\)

现在你的任务是将指定的正整数 \(n\) 分解成若干个互不相同的自然数的和,且使这些自然数的乘积最大。

【输入】

只一个正整数 \(n\),(\(3\le n\le 10000\))。

【输出】

第一行是分解方案,相邻的数之间用一个空格分开,并且按由小到大的顺序。

第二行是最大的乘积。

【输入样例】

10

【输出样例】

2 3 5
30

【核心思想】

  1. 问题分析:给定正整数 \(n\)\(3 \le n \le 10000\)),要求将其分解为若干个互不相同的自然数之和,使得这些数的乘积最大,并输出分解方案和最大乘积。这是一个经典的整数拆分最大乘积问题,结论是:尽可能拆分成从 2 开始的连续整数序列,然后调整余数。

  2. 算法选择

    • 贪心构造:从 \(2\) 开始依次取数 \(2, 3, 4, \dots\),直到剩余数 \(r\) 小于下一个要取的数。然后将余数 \(r\) 从序列的末尾开始依次加 1(优先加在较大的数上),以保证乘积最大。若余数恰好等于最后一个数,则令最后一个数加 1(即 \(r\) 全部加给最后一个数)。
    • 高精度乘法:由于 \(n\) 最大 10000,拆分的数乘积可能极大(远超 64 位),需要使用高精度乘法计算乘积。
  3. 关键步骤

    • 特判:若 \(n \le 4\),直接输出 \(n\)\(n\)(因为 \(4=2+2\) 但要求互不相同,所以 \(4=1+3\) 乘积 \(3\),而 \(4\) 本身乘积 \(4\) 更大,但题目要求分解成若干互不相同的自然数,且 \(3\le n\le 10000\)\(n=4\) 时最优为 \(4\) 本身?但代码中特判输出 \(n\)\(n\),表示不分解)。
    • 构造序列
      • \(i=2\) 开始,只要剩余 \(n \ge i\),就减去 \(i\),将 \(i\) 加入序列,\(i++\)
      • 循环结束后,剩余 \(r\)\(0 \le r < i\))。
      • \(r\) 从序列末尾开始依次加 1(每个数最多加 1),直到 \(r=0\)。若 \(r\) 等于当前要加的数,则直接加到最后一个数上。
    • 高精度乘法:将序列中的每个数转换成字符串,依次相乘。
    • 输出:第一行输出分解方案(升序),第二行输出乘积。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(\sqrt{n} \cdot \text{高精度乘法复杂度})\)
    • 空间复杂度:\(O(\sqrt{n} + \text{高精度位数})\)
  5. 贪心构造 + 高精度

    • 贪心原理:为了乘积最大,应使拆分的数尽可能接近 \(e\)(约 2.718),且避免 1 出现。从 2 开始连续取数是最优策略。
    • 余数分配:将余数均匀加到较大的数上,因为增加大数对乘积的贡献更大。
    • 高精度必要性\(10000\) 的拆分乘积位数很大(约 \(e^{10000/e}\) 的量级),必须用高精度。
    • 适用场景:这类“整数拆分最大乘积”问题,是数论与贪心的经典结合。

【算法标签】

普及 #高精度

【代码详解】

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

const int L = 500; // 定义高精度计算的最大位数

// 高精度乘法函数:计算两个字符串表示的大整数的乘积
string mul(string a, string b)
{
    string s; // 存储结果
    int na[L], nb[L], nc[L]; // na, nb 存储两个乘数的每一位,nc 存储乘积的每一位
    int La = a.size(), Lb = b.size(); // La 和 Lb 分别是两个乘数的长度
    fill(na, na + L, 0); // 初始化 na 数组为 0
    fill(nb, nb + L, 0); // 初始化 nb 数组为 0
    fill(nc, nc + L, 0); // 初始化 nc 数组为 0

    // 将字符串 a 转换为整数数组 na(逆序存储)
    for (int i = La - 1; i >= 0; i--) na[La - i] = a[i] - '0';
    // 将字符串 b 转换为整数数组 nb(逆序存储)
    for (int i = Lb - 1; i >= 0; i--) nb[Lb - i] = b[i] - '0';

    // 高精度乘法核心计算
    for (int i = 1; i <= La; i++)
        for (int j = 1; j <= Lb; j++)
            nc[i + j - 1] += na[i] * nb[j]; // 逐位相乘并累加到结果数组

    // 处理进位
    for (int i = 1; i <= La + Lb; i++)
	{
        nc[i + 1] += nc[i] / 10; // 进位
        nc[i] %= 10; // 取个位数
    }

    // 将结果数组转换为字符串
    if (nc[La + Lb]) s += nc[La + Lb] + '0'; // 如果最高位不为 0,添加到结果
    for (int i = La + Lb - 1; i >= 1; i--)
        s += nc[i] + '0'; // 从高位到低位依次添加

    return s; // 返回结果字符串
}

// 将整数转换为字符串
string f(int a)
{
    char ch[10], t; // ch 用于存储字符形式的数字
    int i = 0;
    int j;
    // 将整数 a 转换为字符数组(逆序)
    while (a)
	{
        ch[i] = a % 10 + '0'; // 取个位数并转换为字符
        a /= 10; // 去掉个位数
        i++;
    }
    ch[i] = '\0'; // 添加字符串结束符

    // 将字符数组倒序,得到正确的字符串
    for (i = i - 1, j = 0; j <= i / 2; j++, i--)
	{
        t = ch[i];
        ch[i] = ch[j];
        ch[j] = t;
    }

    return ch; // 返回字符串
}

int n, a[1005]; // n 是输入的整数,a 数组存储拆分后的整数
string s[1005]; // s 数组存储拆分后的整数的字符串形式
int c = 1; // c 记录拆分后的整数个数

int main()
{
    string m = "1"; // m 用于存储乘积结果,初始值为 1

    // 输入
    cin >> n;

    // 特判:如果 n 小于等于 4,直接输出 n 和 n
    if (n <= 4)
	{
        cout << n << endl << n;
        return 0;
    }

    // 拆项:将 n 拆分为若干个整数,使得这些整数的乘积最大
    for (int i = 2; i <= n; i++)
	{
        if (n >= i)
		{
            n -= i; // 从 n 中减去 i
            a[c++] = i; // 将 i 存入 a 数组
            s[c - 1] = f(i); // 将 i 转换为字符串并存入 s 数组
        }
		else
            break; // 如果 n 不足以减去 i,退出循环
    }

    // 如果有余数,从最高位开始加一
    for (int i = c - 1; i >= 1; i--)
	{
        if (n > 0)
		{
            a[i]++; // 当前位加一
            s[i] = f(a[i]); // 更新字符串形式
            n--; // 余数减一
        }
    }

    // 如果还有余数,让最后一个数加一
    if (n > 0)
	{
        a[c - 1]++;
        s[c - 1] = f(a[c - 1]);
    }

    // 高精度乘法计算拆分后的整数的乘积
    for (int i = 1; i < c; i++)
	{
        cout << a[i] << " "; // 输出拆分后的整数
        m = mul(s[i], m); // 计算乘积
    }

    // 输出结果
    cout << endl << m;
    return 0;
}

【运行结果】

10
2 3 5
30
posted @ 2026-08-31 09:24  团爸讲算法  阅读(24)  评论(0)    收藏  举报