4.1 11091 最优自然数分解问题

第四单元贪心算法

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;

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值