今天在学校群里有人问到这个问题:
输入任意一个正整数N,将其分成多个互不相同的整数,和为N,乘积最大。写出C/C++代码。
贪心策略:要使乘积做大,尽可能地将指定的n(n>4)拆分成从2开始的连续的自然数的和,如果最后有剩余的数,将这个剩余的数在优先考虑后面项的情况下平均分给前面的各项。
例:n=10,先拆分为:10=2+3+4+1,最后一项为1,比4小,将其分配给前面的一项,得到10=2+3+5,所以最大的乘积为2*3*5=30.
n=20,拆分为:20=2+3+4+5+6,刚刚好,最大乘积为2*3*4*5*6=720.
n=26,拆分为:26=2+3+4+5+6+6,因为最后一项为6,不比最后的第二项大,所以将其平均分给前面的项,优先考虑后面的项,即前面的4项各分到1,第5项,分到2,最后是26=3+4+5+6+8,所以最大的乘积为3*4*5*6*8=2880.
基本算法描述如下:
1. 拆分过程
拆分的数a先取2;
当n>a 时做
begin
选择a作为一项;
a增加1;
n减少a;
end;
如果n>0,那么将n从最后一项开始平均分给各项;
如果n还大于0,再从最后一项开始分一次;
2. 求乘积
设置一个数组来存放乘积,初始状态位数为1,结果为1;
将上面拆分的各项依次跟数组各项相乘并考虑进位;
下面是在网上找到的代码
代码如下:
#include<iostream.h> int d[1000],num; long number; void divide(int k,int n) { int i,j; for(i=d[k-1]+1;i*2<n;i++) { d[k]=i; if((n-i)>d[k]*2) divide(k+1,n-i); d[k+1]=n-i; number++; cout<<endl<<"NO."<<number<<"="; for(j=1;j<=k;j++) cout<<d[j]<<"+"; cout<<d[j]; } } void main() { int i,j; cout<<"Input the number to divide:"; cin>>num; d[0]=0; divide(1,num); }
第二种算法:
#include <iostream> #include <ctime> using namespace std; int count1=0; void print(int b[],int n) { int j; count1++; cout<<n+1<<"="; for(j=0;j<n-1;j++) if(b[j]!=0) { cout<<b[j];break;} for(j=j+1;j<n;j++) if(b[j]!=0) cout<<"+"<<b[j]; cout<<endl; } void GetPowerSet(int i,int a[],int b[],int n) { //功能:求以数组a[i,..,n-1]中元素为集合中元素的集合的冥集 //在对字符串中元素进行运算时要注意字符串的结束符'\0'也算是集合中的元素 //具体处理的方法见上例 int j,sum=0; if(i==n) { for(j=0;j<n;j++) sum+=b[j]; if(sum==n+1) print(b,n); } else { for(j=0;j<n;j++) sum+=b[j]; if(sum==n+1) print(b,n); if(sum<n+1) { b[i]=a[i]; GetPowerSet(i+1,a,b,n); b[i]=0; //b[i]所重新赋的值必须是a中不曾出现的元素 GetPowerSet(i+1,a,b,n); } } } int main() { int N; time_t time1,time2; cout<<"输入你要分解的数字:"; cin>>N; time1=time(NULL); int *a=new int[N-1]; int *b=new int[N-1]; for(int i=0;i<N-1;i++) { a[i]=i+1; b[i]=0; } GetPowerSet(0,a,b,N-1); cout<<"有"<<count1<<"种分解方法"<<endl; time2=time(NULL); cout<<difftime(time2,time1)<<"花费"; system("pause"); return 0; }
这个题目的一个变种是:一个正整数N,拆成任意的正整数之和,怎样使这些数的乘积最大?
1 先看一个原则,相等的数相乘最大
n*n > (n-1)*(n+1)=n*n-1
所以相等的2个数相乘,乘积最大
2 看看前面的几个数字
对于和为6
2*2*2 = 8
3*3 = 9 所以3的大
4 我们不考虑了,因为2*2=4 他是平衡点
5 本身就没有 2×3 大,
5以上的就更不用考虑了,因为随便拆开都比他大
所以3是拐点。
也就是尽可能拆分成3,乘积最大
首先,不要拆出1来了,拆出1来就是浪费
其次,不要拆出5以上的数来,不然还不如继续拆分,比如5拆成2*3就比原来的5要大
再次,如果拆出4来,干脆拆成两个2好了,反正都一样
所以,只要考虑拆出多少个2和3来就可以了。
再来,每三个2可以换成两个3,这样3*3 > 2*2*2,所以,尽量拆成3最好了,这样100最后就拆成了32个3和2个2
有关数学分析:
容易说明,拆成n个数时候,变成(N/n)^n最大,那么就是求x使得f=(N/x)^x最大,f’=(N/x)^x(-1+ln(N/x))就是说N/x=e时候最大.
用多元函数求边际极值方法做的,就是用拉格朗日乘数法,设被分解的正整数是N,分的个数为n,并且n<N,n、N为正整数,可以设N=n+t,t可以认为N与n相差的数,对最大值(N/n)^n=(1+t/n)^n求极限,可以得到e^t,所以t越大,e^t越大,因为t是N与n的差,正整数N与分的分数n差越多,最大值越大,如果t=0,即N分成N个1,乘积就是1.