11091 最优自然数分解问题(优先做)
时间限制:1000MS 代码长度限制:10KB
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC;VC;JAVA
Description
问题描述:设n是一个正整数。
(1)现在将n分解为若干个互不相同的自然数之和,且使这些自然数的乘积最大。
(2)现在将n分解为若干个自然数之和,且使这些自然数的乘积最大。
编程任务:对于给定的正整数n,编程计算问题(1)和(2)的最优分解的最大乘积。
注意:
- 这里的自然数不含0但允许为1。
- 特别地,当整数n无法分解为若干互不相同的加数时,即自身视为单独的一个加数,
比如输入2,问题(1)的解输出为2。而如果整数n可以分解为若干互不相同的加数时,
不考虑自身为单独加数的情况,比如4,问题(1)的解输出为3,而非4。 - 若干互不相同自然数或若干自然数,这个若干可>=1,也就是可以为1。
输入格式
只有一个正整数n(1<=n<=100)。
输出格式
输出待解问题(1)和(2)的最大乘积,中间空格相连,这两个数可能较大请皆用64位整数。
如,输入n为10,若加数互不相同,则n=2+3+5,此时最大乘积为235=30。
若加数可相同,则n=2+2+3+3,此时最大乘积为223*3=36。
输入样例
10
输出样例
30 36
解题思路
贪心算法
(1)分解为互不相同自然数之和
注意到: 若a+b等于一个常数,则|a-b|越小,ab就越大。
要使得加数互不相同,又尽可能集中,那加数只能是连续的自然数了。
贪心策略:当n等于1至4时单独处理。n大于4时,将n分成从2开始的连续的自然数的和。如果最后剩下一个数,将此剩余数在后项优先的方式下均匀地分给前面各项。
(2)分解为若干自然数之和
注意到: 若a+b等于一个常数,则|a-b|越小,ab就越大。
若 n = m1+m2+…+mk,则 -1 <= (mi-mj) <= 1,(1<=i<=k, 1<=j<=k),
即任意加数的差距不超过正负1。
由于拆分的加数可以相同,任何一个数拆后乘积总比不拆强,因此拆到极尽,
极尽的加数为3或2,且拆为3比拆为2好,因此优先拆为3。
贪心策略:
极尽拆解,尽可能先将n拆成3,3,3,…,3;若拆成若干3后还有剩余,则为2,或2和2。
归纳公式如下:
- max{m1m2…*mk} = 3 ^ (n/3) if n(mod 3)等于0
- max{m1m2…*mk} = 4 * 3 ^ [(n-4)/3] if n(mod 3)等于1
- max{m1m2…*mk} = 2 * 3 ^ [(n-2)/3] if n(mod 3)等于2
算法思路
分解为互不相同自然数之和
- 列出 n 为 1~4 的特殊处理
- 将 n 拆成从2开始的连续自然数的和,拆不了就退出循环。比如 10 拆成 2,3,4,由于无法继续拆成5,所以退出循环,此时剩余数为1。
- 将剩余数从后往前分配
- 将数组中的数依次相乘
ll one(int n) {
if(n == 1)
return 1;
else if(n == 2)
return 2;
else if(n == 3)
return 2;
else if(n == 4)
return 3;
vector<int> a;
int i = 2, sum = 0;
// 将 n 拆成从2开始的连续自然数的和
// 比如 10 拆成 2,3,4,由于无法继续拆成5,所以退出循环,此时剩余数为1
while(sum + i <= n) {
sum += i;
a.push_back(i);
i++;
}
i = a.size() - 1; // 分配的话,从最后一个自然数开始分配
int leave = n - sum; // leave 为最后剩下的数,准备分配给前面的连续自然数
while(leave) {
a[i]++;
i--; // 索引减1,准备分配给前一个自然数
leave--;
}
ll res = 1;
for(i = 0; i < a.size(); i++) {
res *= a[i];
}
return res;
}
分解为相同自然数之和
递归,类似于剪绳子的解题思路:剑指 Offer 14- I. 剪绳子 动态规划 /贪心算法(以及贪心算法的一些奇特优化思路)
- 长度为4时,剪为两个2是最好
- 长度为3时,不剪时是最好
- 长度为2时,不剪时是最好
- 其他更长长度的情况,将每次划分出3,然后继续递归即可
ll two(int n) {
if(n == 4)
return 2 * 2;
else if(n == 3)
return 3;
else if(n == 2)
return 2;
else
return 3 * two(n - 3);
}
更多注释可查看下方的完整代码中,有助于理解。
代码如下
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
ll one(int n) {
if(n == 1)
return 1;
else if(n == 2)
return 2;
else if(n == 3)
return 2;
else if(n == 4)
return 3;
vector<int> a;
int i = 2, sum = 0;
// 将 n 拆成从2开始的连续自然数的和
// 比如 10 拆成 2,3,4,由于无法继续拆成5,所以退出循环,此时剩余数为1
while(sum + i <= n) {
sum += i;
a.push_back(i);
i++;
}
i = a.size() - 1; // 分配的话,从最后一个自然数开始分配
int leave = n - sum; // leave 为最后剩下的数,准备分配给前面的连续自然数
while(leave) {
a[i]++;
i--; // 索引减1,准备分配给前一个自然数
leave--;
}
ll res = 1;
for(i = 0; i < a.size(); i++) {
res *= a[i];
}
return res;
}
ll two(int n) {
if(n == 4)
return 2 * 2;
else if(n == 3)
return 3;
else if(n == 2)
return 2;
else
return 3 * two(n - 3);
}
int main()
{
int n;
cin >> n;
cout << one(n) << " " << two(n);
return 0;
}
最后
对我感兴趣的小伙伴可查看以下链接
- 我的掘金主页:https://juejin.cn/user/1302297507801358
- 博客主页:http://blog.zhangjiancong.top/
- 公众号:Smooth前端成长记录