题目
求最大积
【问题描述】
输入正整数n(n<100000),将n分解为若干个正整数之和,求分解方案中乘积的最大值。
【输入格式】
只有一行,就是正整数n。
【输出格式】
只有一行,乘积的最大值。
【样例输入】
15
【样例输出】
243
分析
题意:将一个数分成若干个数,使它们的乘积最大。
思路
我最初的思路:
我们先假设只用拆成两个加数,那么这两个数越接近,它们的乘积越大。
那么我们拆开得的两个数也可以继续拆分呀,所以我们一直重复以上的步骤即可。
举个例子画二叉树:
在此二叉树中,我们可以找到最大乘积3*2=6
。
但是,我们从上往下拆分数字非常麻烦,如上图都出现了第2.1轮
、第2.2轮
拆分。
那么我们不妨换一个思路,不是从下往上遍历,而是分组:
我们不难发现,在如上拆分15
的过程中,包含了两次拆分2
、一次拆分3
;
那么,我们就可以从最小的数字(2
)开始往大(直到遍历到了输入的数字)的遍历,每次遍历,就找到并求出遍历的当前数字下两个数的最大乘积,然后贴心的存储在一个数组中备用;
后面遍历到的更大的数字就可以直接调用前面备用好的最大乘积。
如下图,区域B
与区域C
重复,那么我们就可以将区域B
(或区域C
)的值提前保存到一个数组当中,
当执行区域A
时,就可以直接从数组中拿备用好的区域B
(或区域C
)的值,就不需要多次重复执行了。
于是,我们运行程序后得到的数组基本就是这样的:
数字 | 该数字下找到的最大乘积 |
---|---|
2 | 1*1=2 |
3 | 1*2=3 |
4 | 2*2=4 |
5 | 2*3=6 |
6 | 3*3=9 |
…… | …… |
很明显有一个问题:一个数字本身也可以是它自己的加数。
这也不难,给数组赋值时加一个判断即可。
那么最后得到的数组就长这样:
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
值 | 1 | 2 | 3 | 4 | 6 | 9 | 12 | 16 | 24 | 36 | 54 | 81 | 108 | 144 | 192 |
很遗憾,思路对了,答案错了。
考试后我恍然大悟改改正了一点思路和代码:
15的最大乘积是 243 243 243,即是 3 5 3^5 35。这可不是说要用到五叉树,其实二叉树是可以实现的。
回顾我们最初的思路,要找到两个最接近的数,其实这就很像完全二叉树;而15这个例子是不完全二叉树: 15 = 6 + 9 = ( 3 + 3 ) + ( 3 + 6 ) = ( 3 + 3 ) + ( 3 + ( 3 + 3 ) ) = 3 + 3 + 3 + 3 + 3 15=6+9=(3+3)+(3+6)=(3+3)+(3+(3+3))=3+3+3+3+3 15=6+9=(3+3)+(3+6)=(3+3)+(3+(3+3))=3+3+3+3+3
看,第一步就是不完全的。
不过好在,这个问题很好改——前面是直接找最接近的数字,直接除以二;现在把这一步改成循环,遍历当前二叉树的所有可能,然后在循环内层求积,并每次取最大值即可。
这样我们就能成功且正确的得到:
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
值 | 1 | 2 | 3 | 4 | 6 | 9 | 12 | 18 | 27 | 36 | 54 | 81 | 108 | 162 | 243 |
完美。(当然,绝对的完美还需要配合上高精度。)
代码
-
主函数先写上
int main(){ return 0; }
-
就依题意,先输入
n
。
因为我不展示用高精度,所以我就用long long
类型。#include<cstdio> //scanf() long long n; int main(){ scanf("%lld", &n); return 0; }
-
接下来就可以遍历1(或2)到n找最大乘积了。
#include<cstdio> //scanf() #include<cmath> //fmax() long long n, s[100001]; int main(){ scanf("%lld", &n); for(int i=1; i<=n; i++){ s[i]=i; for(int j=1; j<=i/2; j++){ s[i]=fmax(s[j]*s[i-j], s[i]); } } return 0; }
首先,把遍历到的数赋值给数组的当前位,然后遍历当前数字下的所有可能的二叉树,不断取最大值。
-
最后,很愉快地输出数组第n位即可!
#include<cstdio> //scanf(), printf() #include<cmath> //fmax() long long n, s[100001]; int main(){ scanf("%lld", &n); for(int i=1; i<=n; i++){ s[i]=i; for(int j=1; j<=i/2; j++){ s[i]=fmax(s[j]*s[i-j], s[i]); } } printf("%lld", s[n]); return 0; }
答案
(满分答案肯定是要用到高精度的;我这里只讲代码思路,所以用的是低精度。)
#include<cstdio>
#include<cmath>
long long n, s[100001];
int main(){
scanf("%lld", &n);
for(int i=1; i<=n; i++){
s[i]=i;
for(int j=1; j<=i/2; j++){
s[i]=fmax(s[j]*s[i-j], s[i]);
}
}
printf("%lld", s[n]);
return 0;
}
尾声
这次是自学(现场自己推导)二叉树啊(我现在还没学二叉树呢),我还不知道我这是不是正宗的二叉树。下次上课时听老师讲讲这道题。