题解:洛谷 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
【核心思想】
-
问题分析:给定正整数 \(n\)(\(3 \le n \le 10000\)),要求将其分解为若干个互不相同的自然数之和,使得这些数的乘积最大,并输出分解方案和最大乘积。这是一个经典的整数拆分最大乘积问题,结论是:尽可能拆分成从 2 开始的连续整数序列,然后调整余数。
-
算法选择:
- 贪心构造:从 \(2\) 开始依次取数 \(2, 3, 4, \dots\),直到剩余数 \(r\) 小于下一个要取的数。然后将余数 \(r\) 从序列的末尾开始依次加 1(优先加在较大的数上),以保证乘积最大。若余数恰好等于最后一个数,则令最后一个数加 1(即 \(r\) 全部加给最后一个数)。
- 高精度乘法:由于 \(n\) 最大 10000,拆分的数乘积可能极大(远超 64 位),需要使用高精度乘法计算乘积。
-
关键步骤:
- 特判:若 \(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\) 等于当前要加的数,则直接加到最后一个数上。
- 高精度乘法:将序列中的每个数转换成字符串,依次相乘。
- 输出:第一行输出分解方案(升序),第二行输出乘积。
-
时间/空间复杂度:
- 时间复杂度:\(O(\sqrt{n} \cdot \text{高精度乘法复杂度})\)。
- 空间复杂度:\(O(\sqrt{n} + \text{高精度位数})\)。
-
贪心构造 + 高精度:
- 贪心原理:为了乘积最大,应使拆分的数尽可能接近 \(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
浙公网安备 33010602011771号