LuoGu P1249 最大乘积(数论 + 前缀和)


P1249 最大乘积

题目描述

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

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

输入格式

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

输出格式

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

第二行是最大的乘积。

输入输出样例

输入 #1 复制

10

输出 #1 复制

2 3 5

30

分析过程

题目需要分解一个自然数,使得分解之后的数字相乘得到的数字最大,输出该组数字,并且输出乘积结果。
首先最后输出乘积是高精度乘法,现在要获得分解一个数字乘积的最大值的组合。因为数字不能够重复,所以说分解的数字越多乘积则就最大,所以从2~n做一遍扫描,比如9分解出来的最大的组合应该为[2,3,4]。但是有可能会出现有余数的情况,例如需要分解n = 12,扫描得到结果[2,3,4]余3,3可以有很多种分配方法:

1
2
3
1. 一个个向前分配,得到结果[3,4,5] 乘积的结果为60。  
2. 直接分配到最大的数字得到结果[2,3,7] 乘积结果为42。  
3. 再向后分配一个数字得到[2,3,4,5]超出了2,再删除2,得到结果序列[3,4,5],可见于方案一一样。

从上述的几种策略可以看出,直接分配到最大的数字并不靠谱,方案1、3实际上是同一种方案,③是对①的简化。

需要注意的是,当例子是10的时候,得到序列[2,3,4]余1,在这里我们进行③的操作得到,[2,3,4,5]超出4删除4得到[2,3,5]。
然后当例子n为8时,我们得到[2,3]余3,进行③操作得到,[2,3,4]超出1,本来应该是删除1,但是序列中并没有1,我们选择先删去2,然后最后一个数字加上1得到序列[3,5]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include<bits/stdc++.h>

using namespace std;


string mul(string sa, int b) {
    string res;
    vector<int> a, c;
    for (size_t i = sa.size() - 1; ~i; i--) a.push_back(sa[i] - '0');
    int t = 0;
    for (int i = 0; i < a.size(); i++) {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    while (t) {
        c.push_back(t % 10);
        t /= 10;
    }
    for (int i = c.size() - 1; ~i; i--) res += c[i] + '0';
    return res;
}

int d[10010];


void process(int n) {
    int s = 0;
    for (int i = 2; i <= n; i++) {
        s += i;
        d[i] = true;
        if (s > n) {
            if (s - n == 1) {
                d[2] = false;
                d[i] = false;
                d[i + 1] = true;
                break;
            }
            d[s - n] = false;
            break;
        }
    }
}

string solve() {
    string s = "1";
    for (int i = 0; i < 10010; i++) {
        if (d[i]) {
            s = mul(s, i);
        }
    }
    return s;
}

int main() {
    ios::sync_with_stdio(false);
    //freopen("in.txt", "r", stdin);
    int n;
    cin >> n;
    process(n);
    for (int i = 0; i < 10010; i++) {
        if (d[i]) cout << i << " ";
    }
    cout << endl;
    cout << solve() << endl;
    return 0;
}

值得关注的地方在于
如果把题目中间的数字不重复的条件删去,那么就会有几个新的思路。
我在查资料的过程中,找到一个和我思路一样的流程,放在此时

  1. a 不能分成1 与 a-1 的和。
  2. a <= 4 时,分解后的积 <= a, 当且仅当 4 = 2 +2 时相等。
  3. a分为两个数之和,并且这两个数的乘积最大. x= a/2, y = a-x, 继续分x, y。递归。

另一边,把n分解成几个自然数的和,并且使得乘积最大有以下结论。

关键是明白:把一个自然数N拆分成若干个自然数的和,只有当这些分拆数由2或3组成,其中2最多为2个时,这些分拆数的乘积最大。

简而言之,就拆3就拆3,三个2可以变成两个3,因为2 + 2 + 2 等于 3 + 3,且 3 * 3 > 2 * 2 * 2。
有人证明结果最好的分割是自然对数e,3是离自然对数最近的一个自然数。