最优分解问题

问题

将正整数 n 分解为若干互不相同的自然数的和,使这些自然数的乘积最大

思路

以下内容摘自老师给的 PPT:

对整数分析可有结论: 若 a + b = const, 则 | a – b | 越⼩,a * b 越⼤。 根据原问题的描述,需要将正整数 n 分解为若干互不相同的自然数的和,同时又要使⾃然数的乘积最⼤。 当 n < 4 时对 n 的分解的乘积是小于 n 的; 当 n ⼤于或等于 4 时,n = 1 + ( n – 1 ) 因⼦的乘积也是⼩于 n 的, 所以 n = a + ( n – a ), 2 ≤ a ≤ n - 2, 可以保证乘积⼤于 n, 即越分解乘积越⼤。 因此可以采用如下贪⼼策略: 将 n 分成从 2 开始的连续⾃然数的和,如果最后剩下⼀个数,将此数在后项优先的⽅式下均匀地分给前⾯面各项。 该贪⼼策略⾸先保证了正整数所分解出的因⼦之差的绝对值最⼩,即 | a – b | 最⼩; 同时又可以将其分解成尽可能多的因子,且因⼦的值较⼤,确保最终所分解的⾃然数的乘积可以取得最⼤值。

代码

使用了这个库:The GNU Multiple Precision Arithmetic Library

#include <gmp.h>

/*
 * 假设 n = 13
 * sum = 2 + 3 + 4 (last = 4), 13 - sum = 4 (left = 4)
 *
 * 假设 n = 9
 * sum = 2 + 3 + 4 (last = 4), 9 - sum = 0 (left = 0)
 */
void get_last_left(long *last, long *left, long n)
{
    long i, sum = 0;
    for (i = 2; sum <= n; i++) {
        sum = sum + i;
    }
    i--;

    *last = i - 1;
    *left = n - (sum - i);
}

/*
 * 计算累积,从 start 到 end, 步长为 1
 *
 * 如果 start > end, 返回 1
 */
void cumulate(mpz_t rnt, long start, long end)
{
    mpz_set_si(rnt, 1);
    for (long i = start; i <= end; i++) {
        /*pro = pro * i;*/
        mpz_mul_si(rnt, rnt, i);
    }
}

/*
 * 计算一个正整数的最优分解问题
 *
 * n 必须为正整数,且小于或等于 LONG_MAX
 */
void optimal_decomposition(mpz_t rnt, long n)
{
    switch (n) {
    case 1: mpz_set_si(rnt, 0); return;     /*  0 * 1  */
    case 2: mpz_set_si(rnt, 0); return;     /*  0 * 2  */
    case 3: mpz_set_si(rnt, 2); return;     /*  1 * 2  */
    case 4: mpz_set_si(rnt, 3); return;     /*  1 * 3  */
    }

    long last, left;
    get_last_left(&last, &left, n);

    mpz_t pro;
    mpz_init(pro);
    if (last == left) {             /* example, n = 13, 3 * 4 * 6  */
        cumulate(rnt, 3, last);
        mpz_mul_si(rnt, rnt, last + 2);
    } else {                        /* example, n = 11, 2 * 4 * 5  */
        /* (last - 1 - left) 表示未分配到 1 的个数 */
        cumulate(pro, 2, 2 + (last - 1 -left) - 1);
        mpz_set(rnt, pro);
        cumulate(pro, 2 + (last - 1 - left) + 1, last + 1);
        mpz_mul(rnt, rnt, pro);
    }

    mpz_clear(pro);
}