题目
题目描述
一个正整数一般可以分为几个互不相同的自然数的和,如 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=304+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;
}