洛谷P1249 最大乘积

题目

题目描述

一个正整数一般可以分为几个互不相同的自然数的和,如 3=1+2,3=1+2,4=1+3,4=1+3,5=1+4=2+3,6=1+5=2+4。

现在你的任务是将指定的正整数n分解成若干个互不相同的自然数的和,且使这些自然数的乘积最大。

输入格式

只有一个正整数n,(3≤n≤10000)。

输出格式

第一行是分解方案,相邻的数之间用一个空格分开,并且按由小到大的顺序。

第二行是最大的乘积。

示例

输入

10

输出

2 3 5

30

解题思路

在解决这道题的时候我们首先要对题目的内容进行详细分析,题目要求将输入的数n进行拆分,且要求拆分出的数不能重复,之后对拆分出的数做乘积处理。

对于一个数,我们假设拆完后的乘积越大,价值越高,不难发现将其拆出一个1是最没有性价比的行为,因为1乘任何数得到的结果都为那个数本身,那最有性价比的拆法是什么呢?

我们可以用数学归纳法进行一下粗略的估计(在下列情况中可以的话不考虑1,原因如上):

我们拿10作为例子,可以有下列拆法:
2+3+5     →      做乘积2*3*5=30

4+6         →      做乘积4*6=24

14作为例子,可以有下列拆法:

2+3+4+5 →      做乘积2*3*4*5=120

2+12       →      做乘积2*12=24

3+5+6         →      做乘积3*5*6=90

由上不难看出,一个数拆分得越细,做乘积后的数就越大。

不过细心的你应该可以发现,上述两数在拆分到最细时都没有余数出现,那么有余数的时候怎么处理它嘞。

拿11的拆分做示例:

做2+3+4的拆分后还余下2,这时候我们不难发现,余下的数如果与2相加则会与4重复,剩下的选择只剩下3和4。

若将2给3,实质上就是多出2份2*4,同理,给4的话就是多出两份2*3。

由于题目要的是最大的乘积,所以我们从其实质不难看出,将多出来的数给尽量小的值所得到的收益最大

综上述,我们对如何拆分数便有了充分的了解,这边的代码如下:

    //对输入的数进行拆分
	for(int i = 1; n > 0;i++)
	{
        //从2开始对原本的数n进行拆分
		if (n >= i + 1)
		{
			divide[i] = i + 1;
			n = n - i - 1;
		}
        //出现余数的时候的处理
		else
		{
			if (n < length) {
				position = length + 1 - n;
				divide[length + 1 - n] += n;
			}
			else
			{
				position = 1;
				//考虑4和3的特殊情况
				if (divide[1] + n == a)
				{
					if (n == divide[1])
					{
						divide[2] = n - 1;
						divide[1]++;
					}
					else
						divide[2] = n;
					length++;
					break;
				}
				divide[1] += n;
			}
			n = 0;
			break;
		}
		length++;
	}

这边需要注意的是,为保险起见,我们将拆分出来的数做单个存储,按最大拆分量(即10000都拆1)创建数组divide[10000],需注意该解法divide是从[1]开始做存储。 

可以发现,我这边做拆分都是从2开始的,这就导致3和4这两个数出现了特殊的情况,因此在拆分的时候对这两种特殊情况做了单独处理。

代码中的position则是记录是在哪个位置获取了余数,因为获取余数后该位置的数会成为拆分出的数中最大的数,所以将其单独标记出来,最后再输出。

输出的代码如下:

    //拆分输出
	for (int i = 1; i <= length; i++)
	{	
		if(i != position)
			cout << divide[i] << " ";
	}
    //当拆分的数没有余数的时候,position会处于0的位置
	if (position != 0)
		cout << divide[position] << endl;
	else
		cout << endl;

之后就是对拆分出的数做乘积处理,由于最大值10000的存在,自然使用高精度乘法进行运算。

代码如下:

    //做乘积
	int r_length = 1;
	result[0] = divide[1];
	//外层循环所有拆分的数
	for (int i = 2; i <= length; i++)
	{
		//内层循环对result做乘
		for (int k = 0; k < r_length; k++)
		{
			result[k] *= divide[i];
		}
		//对result的每一位做进位处理
		for (int k = 0; k < r_length; k++)
		{
			result[k+1] += result[k] / 10;
			result[k] %= 10;
		}
		//对最后一位做处理
		while (result[r_length] >= 10)
		{
			result[r_length + 1] = result[r_length] / 10;
			result[r_length] %= 10;
			r_length++;
		}
		if (result[r_length] > 0)
		{
			r_length++;
		}
	}

(这边偷个懒,直接用上一篇文章对高精度乘法的解释(●'◡'●),我的做法实质上大同小异)

这边的乘法为每一个位置上的值都进行乘积处理后,再统一进行进位处理。

看上述代码也可以发现,我的进位是通过将所有的每一位的值都往最后一位堆积,最后循环处理最后一位数就可以完成整体的进位,并实现对结果的长度的更新。

最后对result进行输出就完成了。

完整AC代码

#include<iostream>
#include<string>
using namespace std;
int divide[10000] = { 0 };
int result[5000] = { 0 };

int main()
{
	//输入整数a,赋值给n
	int n = 0, a = 0;
	cin >> a;
	n = a;
	//拆分的数的个数
	int length = 0;
	//最后添加数的位置
	int position = 0;
	
	//对输入的数进行拆分
	for(int i = 1; n > 0;i++)
	{
		if (n >= i + 1)
		{
			divide[i] = i + 1;
			n = n - i - 1;
		}
		else
		{
			if (n < length) {
				position = length + 1 - n;
				divide[length + 1 - n] += n;
			}
			else
			{
				position = 1;
				//考虑4和3的特殊情况
				if (divide[1] + n == a)
				{
					if (n == divide[1])
					{
						divide[2] = n - 1;
						divide[1]++;
					}
					else
						divide[2] = n;
					length++;
					break;
				}
				divide[1] += n;
			}
			n = 0;
			break;
		}
		length++;
	}

	//拆分输出
	for (int i = 1; i <= length; i++)
	{	
		if(i != position)
			cout << divide[i] << " ";
	}
	if (position != 0)
		cout << divide[position] << endl;
	else
		cout << endl;

	//做乘积
	int r_length = 1;
	result[0] = divide[1];
	//外层循环所有拆分的数
	for (int i = 2; i <= length; i++)
	{
		//内层循环对result做乘
		for (int k = 0; k < r_length; k++)
		{
			result[k] *= divide[i];
		}
		//对result的每一位做进位处理
		for (int k = 0; k < r_length; k++)
		{
			result[k+1] += result[k] / 10;
			result[k] %= 10;
		}
		//对最后一位做处理
		while (result[r_length] >= 10)
		{
			result[r_length + 1] = result[r_length] / 10;
			result[r_length] %= 10;
			r_length++;
		}
		if (result[r_length] > 0)
		{
			r_length++;
		}
	}

	//输出乘积结果
	for (int i = r_length - 1; i >= 0; i--)
	{
		cout << result[i];
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值