题目分析
Input:一个正整数 n
Output:一个乘积值 X_max
Constraint:
{
n
i
=
a
+
b
+
c
+
⋯
X
i
=
a
∗
b
∗
c
∗
⋯
X
m
a
x
=
m
a
x
{
X
i
}
w
h
e
r
e
,
a
≠
b
≠
c
≠
⋯
\begin{cases}n_i=a+b+c+\cdots\\\\X_i=a*b*c*\cdots\\\\X_{max}=\rm max\{X_i\}\\\\\end{cases} \\\\\quad\quad where,\ a\ne b\ne c\ne\cdots
⎩
⎨
⎧ni=a+b+c+⋯Xi=a∗b∗c∗⋯Xmax=max{Xi}where, a=b=c=⋯
为了分析方便,不妨假设 a < b < c < ⋯ a<b<c<\cdots a<b<c<⋯
对于示例,给出正整数 10,分法 10 = 2 + 3 + 5 ⟹ X = 2 ∗ 3 ∗ 5 = 30 10 = 2+3+5\implies X=2*3*5=30 10=2+3+5⟹X=2∗3∗5=30 是最大的
贪心策略
考虑一下最简单的分法,将 n n n 划分成两个自然数之和 n = a + b ( a < b ) n=a+b\quad(a<b) n=a+b(a<b)
① 为了让 X = a ∗ b X=a*b X=a∗b 最大,因子 a 、 b a、b a、b 应当都贡献出一份力量,如果其中一方为 1,乘积只有另一方出力,所以令 a ⩾ 2 a\geqslant2 a⩾2
② 利用消元法,得到 X = a ∗ ( n − a ) X=a*(n-a) X=a∗(n−a),使 X X X 是关于 a a a 的函数,显然这是一个二次函数,图像如图所示:
从中发现,当 a = n 2 a = \dfrac{n}{2} a=2n 时取得最大值,换言之, a = b = n 2 a=b=\dfrac{n}{2} a=b=2n
由于 a 、 b a、b a、b 是自然数, n 2 \dfrac{n}{2} 2n 也可能不是整数,因此 a ≠ b a\not=b a=b 时,如果要让 X X X 最大, a、b应尽量接近(贪心策略)
那么三个以上因子的情况呢?
不妨将 n = a + b + c + ⋯ n=a+b+c+\cdots n=a+b+c+⋯ 看作 n = a + ( n − a ) n=a+(n-a) n=a+(n−a),分成两个部分,依然是 X = a ∗ ( n − a ) X=a*(n-a) X=a∗(n−a),要让 X X X 最大,那么 ( n − a ) (n-a) (n−a) 考虑尽量与 a a a 接近
发现有子问题的味道,考虑递归 / 动态规划之类
解法
一、递归(DFS搜索)
注:该算法的时间复杂度依然很大,在测试数据 > > > 55 左右,程序基本 k i l l e d killed killed
因此,该解法只是提供一种思路
或许可以有剪枝的办法降低复杂度,这一点没去想……
DFS大致思路
def DFS(step):
if 终止条件:
output
end
for i <- 每一种选择:
flag = true; % 标记选择
....... % 选择后处理
DFS(step + 1) % 下一步
flag = false % 移除选择
....... % 恢复现场
end
end
问题思路
根据上面的模板:
① 选择是什么?
从 2 到正整数 n
都是一种选择
② 如何保证因子互不重复?
使用一个used[i]
数组表示 i
这个数已经使用
③ 要处理什么?
首先当然是used[i]
要标记为使用
可以考虑设一个数组numbers[]
,用来存放因子选择序列,将当前选择push
④ Step + 1具体是什么?
选择完i
之后,n 要减去 i,即n -= i
,然后进入下一层
⑤ 终止条件是什么?
n == 1
返回 1
n == 2
时,返回 2 (2 = 1 + 1不行,2 = 0 + 2 直接让乘积归零,所以不分)
n == 3
时,返回 2(3 = 1 + 2)
n == 4
时,返回 3(4 = 1 + 3)
设置这么多,是因为这些特殊数据很坑
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
long long maxProduct(int n, vector<bool>& used, vector<int>& numbers) {
if (n == 1) {
return 1;
} else if (n == 2) {
return 2;
} else if (n == 3) {
return 2;
} else if (n == 4) {
return 3;
}
long long maxproduct = 1;
for (int i = 2; i <= n; ++i) {
if (!used[i]) {
used[i] = true;
numbers.push_back(i);
maxproduct = max(maxproduct, i * maxProduct(n - i, used, numbers));
numbers.pop_back();
used[i] = false;
}
}
return maxproduct;
}
int main() {
int n;
cin >> n;
vector<bool> used(n + 1, false);
vector<int> numbers;
cout << maxProduct(n, used, numbers);
return 0;
}
二、贪心算法
问题思路
课本提供的思路如下:
① 从 2 作为因子开始,到3、4、……,不断使得因子 + 1
② 到最后剩下一个余数 R 时,会有以下几种情况:
- 余数 R 比前一个因子小
- 余数 R = 前一个因子
③ 将余数 R 拆成 R = 1 + 1 + ⋯ + 1 R=1+1+\cdots+1 R=1+1+⋯+1,每个 1 赋给前面的所有因子
为什么不全部给某一个因子?
——根据贪心策略,因子越接近,乘积越大。全部给某一个因子将会拉大差距。
- 余数 R 比前一个因子小,从大到小让因子 + 1
- 余数 R = 前一个因子,从大到小让因子 + 1,此时剩下一个 1,再赋给前一个因子
为什么要从大到小?
——如果从小到大,因为因子之间差 1 ,一旦赋 1,如果余数 R 不够让全部因子 + 1,就会出现重复
例如 n = 10,因子有 2 3 4,余 1,如果从小到大,则变成3 3 4,出现重复
代码
#include <iostream>
#include <vector>
using namespace std;
int max_X(int n, vector<int>& factorArray) {
// 初始化因子是 2,remain表示余数,index是数组索引
int factor = 2, remain = 0, index = 0;
// 扣去因子 2
n -= factor;
// 加入到因子选择序列中
factorArray.push_back(factor);
// 因子不断 + 1,并加入到选择序列中
while (n > factorArray[index]) {
factor++;
index++;
factorArray.push_back(factor);
n -= factor;
remain = n;
}
// 处理余数
while (remain > 0) {
if (index < 0)
break;
factorArray[index] += 1;
remain -= 1;
index--;
}
int len = factorArray.size();
long long X = 1;
// remain = 前一个余数时,最后还剩下 1
if (remain > 0) {
factorArray[len - 1]++;
}
// 计算乘积
for (int factor : factorArray) {
X *= factor;
}
return X;
}
int main()
{
int n;
cin >> n;
if (n == 1) {
cout << 1;
} else if (n == 2) {
cout << 2;
} else if (n == 3) {
cout << 2;
} else if (n == 4) {
cout << 3;
} else {
vector<int> factorArray;
cout << max_X(n, factorArray);
}
return 0;
}