第四单元贪心算法
4.1 11091 最优自然数分解问题(必做)
时间限制:1000MS 内存限制:65535K
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC;VC;JAVA
Description
问题描述:设n是一个正整数。
(1)现在将n分解为若干个互不相同的自然数之和,且使这些自然数的乘积最大。
(2)现在将n分解为若干个自然数之和,且使这些自然数的乘积最大。
编程任务:对于给定的正整数n,编程计算问题(1)和(2)的最优分解的最大乘积。
注意:
1. 这里的自然数不含0但允许为1。
2. 特别地,当整数n无法分解为若干互不相同的加数时,即自身视为单独的一个加数,
比如输入2,问题(1)的解输出为2。而如果整数n可以分解为若干互不相同的加数时,
不考虑自身为单独加数的情况,比如4,问题(1)的解输出为3,而非4。
3. 若干互不相同自然数或若干自然数,这个若干可>=1,也就是可以为1。
输入格式
只有一个正整数n(1<=n<=100)。
输出格式
输出待解问题(1)和(2)的最大乘积,中间空格相连,这两个数可能较大请皆用64位整数。
如,输入n为10,若加数互不相同,则n=2+3+5,此时最大乘积为2*3*5=30。
若加数可相同,则n=2+2+3+3,此时最大乘积为2*2*3*3=36。
输入样例
10
输出样例
30 36
提示
分析:
注意无论是(1)还是(2),乘积皆用64位整数表示。
(1)分解为互不相同自然数之和
注意到: 若a+b等于一个常数,则|a-b|越小,ab就越大。
要使得加数互不相同,又尽可能集中,那加数只能是连续的自然数了。
贪心策略:将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。
归纳公式如下:
1)max{m1*m2*...*mk}= 3^(n/3) if n(mod 3)等于0
2)max{m1*m2*...*mk}= 4*3^[(n-4)/3] if n(mod 3)等于1
3)max{m1*m2*...*mk}= 2*3^[(n-2)/3] if n(mod 3)等于2
另外此题所涉的64位整数,如何使用?
编译环境不同,对64位整数的定义和输入输出略有不同:
1) gnu gcc/g++ 中longlong类型,或unsigned long long,
输入输出用cin和cout直接输出,用scanf和printf也可以的(但本OJ系统不支持!)。
long long a;
cin >> a;
cout << a;
也可以使用:(注意一下,本OJ系统的gcc/g++不支持64位整数以"%I64d"形式输出,
但标准gnu gcc是支持如下的,在codeblocks上可以无误运行)
long long a;
scanf("%I64d",&a);
printf("%I64d",a);
2) VC中用__int64类型,或unsigned __int64
__int64 a;
scanf("%I64d",&a);
printf("%I64d",a);
vc下,64整数不要用cin和cout来输入输出,据说vc下64位整数兼容不好,会出错!
大家可测试一下如下程序在vc下是否会出错?
__int64 a;
cin >> a;
cout << a;
#include <iostream>
#include <math.h>
using namespace std;
int main()
{
int n;
cin >> n;
if(n == 1 || n == 2)
{
// 排除n=1,2的情况
cout << n << " " << n; //当n=1/2时,两个都为1/2;(自身视为单独的一个加数)
}else{
// 互不相同自然数之和:贪心策略:将n分成从2开始的连续的自然数的和。
//如果最后剩下一个数,将此剩余数在后项优先的方式下均匀地分给前面各项。
long long _res1 = 1;
if (n == 3 || n == 4) {
// 排除n=3,4的情况,n=3,分为1,2 n=4,分为 1 3
_res1 = n - 1;
}else{
int _count = 0;
int _rest = 0,_avg = 0,_mod = 0;
int a[n + 1];// a[0] 和 a[1]未使用
for(int i = 2; i <= n;i++){ // 初始化a,a数组全为1
a[i] = 1;
}
for(int i = 2; i <= n; i++)
{
_count += i;
if(_count > n)
{
_rest = n - (_count - i);//多出来的部分
if (_rest < (i - 2)) {//不够每个已选的数都分到1
_avg = 1;
_mod = 0;
}else{// 每个已选的数至少能分到1
_avg = _rest / (i -2);// 平均分个前i - 2个已确定的数
_mod = _rest % (i -2);// 平均分后剩下的
}
for(int j = i - 1; j >=i - _rest; j--){
// 从后往前分配多出来的部分
a[j] += _avg;
}
a[i-1] += _mod;//最后一位+_mod
break;
}
a[i] = i;
}
for(int i = 2; i <= n; i++)
{
_res1 *= a[i];
}
}
//若干自然数之和贪心策略:
//极尽拆解,尽可能先将n拆成3,3,3,...,3;若拆成若干3后还有剩余,则为2,或2和2。
/*
归纳公式如下:
1)max{m1*m2*...*mk} = 3^(n/3) if n(mod 3)等于0
2)max{m1*m2*...*mk} = 4*3^[(n-4)/3] if n(mod 3)等于1
3)max{m1*m2*...*mk} = 2*3^[(n-2)/3] if n(mod 3)等于2
*/
long long _res2 = 1;
if(n % 3 == 0){
_res2 = pow(3,n/3);
}else if(n % 3 == 1){
_res2 = 4*pow(3,(n-4)/3);
}else{
_res2 = 2*pow(3,(n-2)/3);
}
cout << _res1 << " " << _res2;
}
cout << endl;
return 0;
}