【C++】2024.03.23题目——求最大积——将n分解为若干个正整数之和,求分解方案中乘积的最大值。

题目

求最大积
【问题描述】
输入正整数n(n<100000),将n分解为若干个正整数之和,求分解方案中乘积的最大值。
【输入格式】
只有一行,就是正整数n。
【输出格式】
只有一行,乘积的最大值。
【样例输入】
15
【样例输出】
243


分析

题意:将一个数分成若干个数,使它们的乘积最大。


思路

我最初的思路:

我们先假设只用拆成两个加数,那么这两个数越接近,它们的乘积越大
那么我们拆开得的两个数也可以继续拆分呀,所以我们一直重复以上的步骤即可。

举个例子画二叉树

第3轮拆分
第2.1轮拆分
第2.2轮拆分
第1轮拆分
输入的数字
1
1
2
1
1
1
2
3
5

在此二叉树中,我们可以找到最大乘积3*2=6

但是,我们从上往下拆分数字非常麻烦,如上图都出现了第2.1轮第2.2轮拆分。

那么我们不妨换一个思路,不是从下往上遍历,而是分组

我们不难发现,在如上拆分15的过程中,包含了两次拆分2、一次拆分3

那么,我们就可以从最小的数字(2)开始往大(直到遍历到了输入的数字)的遍历,每次遍历,就找到并求出遍历的当前数字下两个数的最大乘积,然后贴心的存储在一个数组中备用

后面遍历到的更大的数字就可以直接调用前面备用好的最大乘积

如下图,区域B区域C重复,那么我们就可以区域B(或区域C)的值提前保存到一个数组当中
当执行区域A时,就可以直接从数组中拿备用好的区域B(或区域C)的值,就不需要多次重复执行了。

区域A
区域B
区域C
4
2
2
1
1
1
1

于是,我们运行程序后得到的数组基本就是这样的:

数字该数字下找到的最大乘积
21*1=2
31*2=3
42*2=4
52*3=6
63*3=9
…………

很明显有一个问题:一个数字本身也可以是它自己的加数
这也不难,给数组赋值时加一个判断即可。
那么最后得到的数组就长这样:

下标123456789101112131415
123469121624365481108144192

很遗憾,思路对了,答案错了。
考试后我恍然大悟改改正了一点思路和代码:

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

15
6
9
3
3
3
6
3
3

看,第一步就是不完全的。

不过好在,这个问题很好改——前面是直接找最接近的数字,直接除以二;现在把这一步改成循环,遍历当前二叉树的所有可能,然后在循环内层求积,并每次取最大值即可。

这样我们就能成功且正确的得到:

下标123456789101112131415
123469121827365481108162243

完美。(当然,绝对的完美还需要配合上高精度。)


代码

  1. 主函数先写上

    int main(){
    	return 0;
    }
    

  2. 就依题意,先输入n
    因为我不展示用高精度,所以我就用long long类型。

    #include<cstdio>	//scanf()
    long long n;
    int main(){
    	scanf("%lld", &n);
    	return 0;
    }
    

  3. 接下来就可以遍历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;
    }
    

    首先,把遍历到的数赋值给数组的当前位,然后遍历当前数字下的所有可能的二叉树不断取最大值


  4. 最后,很愉快地输出数组第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;
}

尾声

这次是自学(现场自己推导)二叉树啊(我现在还没学二叉树呢),我还不知道我这是不是正宗的二叉树。下次上课时听老师讲讲这道题。


高精度加减法
高精度乘法


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值