4-15 最优分解问题(算法设计与分析)

image-20240412182130582



题目分析

Input:一个正整数 n

Output:一个乘积值 X_max

Constraint:
{ n i = a + b + c + ⋯ X i = a ∗ b ∗ c ∗ ⋯ X m a x = m a x { X i } w h e r e ,   a ≠ b ≠ c ≠ ⋯ \begin{cases}n_i=a+b+c+\cdots\\\\X_i=a*b*c*\cdots\\\\X_{max}=\rm max\{X_i\}\\\\\end{cases} \\\\\quad\quad where,\ a\ne b\ne c\ne\cdots ni=a+b+c+Xi=abcXmax=max{Xi}where, a=b=c=

为了分析方便,不妨假设 a < b < c < ⋯ a<b<c<\cdots a<b<c<


对于示例,给出正整数 10,分法 10 = 2 + 3 + 5    ⟹    X = 2 ∗ 3 ∗ 5 = 30 10 = 2+3+5\implies X=2*3*5=30 10=2+3+5X=235=30 是最大的


贪心策略

考虑一下最简单的分法,将 n n n 划分成两个自然数之和 n = a + b ( a < b ) n=a+b\quad(a<b) n=a+b(a<b)

① 为了让 X = a ∗ b X=a*b X=ab 最大,因子 a 、 b a、b ab 应当都贡献出一份力量,如果其中一方为 1,乘积只有另一方出力,所以令 a ⩾ 2 a\geqslant2 a2

② 利用消元法,得到 X = a ∗ ( n − a ) X=a*(n-a) X=a(na),使 X X X 是关于 a a a 的函数,显然这是一个二次函数,图像如图所示:

untitled

从中发现,当 a = n 2 a = \dfrac{n}{2} a=2n 时取得最大值,换言之, a = b = n 2 a=b=\dfrac{n}{2} a=b=2n

由于 a 、 b a、b ab 是自然数, n 2 \dfrac{n}{2} 2n 也可能不是整数,因此 a ≠ b a\not=b a=b 时,如果要让 X X X​ 最大, a、b应尽量接近(贪心策略)

那么三个以上因子的情况呢?

不妨将 n = a + b + c + ⋯ n=a+b+c+\cdots n=a+b+c+ 看作 n = a + ( n − a ) n=a+(n-a) n=a+(na),分成两个部分,依然是 X = a ∗ ( n − a ) X=a*(n-a) X=a(na),要让 X X X 最大,那么 ( n − a ) (n-a) (na) 考虑尽量与 a a a 接近

发现有子问题的味道,考虑递归 / 动态规划之类



解法

一、递归(DFS搜索)

注:该算法的时间复杂度依然很大,在测试数据 > > > 55 左右,程序基本 k i l l e d killed killed

因此,该解法只是提供一种思路
或许可以有剪枝的办法降低复杂度,这一点没去想……

DFS大致思路

def DFS(step):
	if 终止条件:
		output
	end
    
    for i <- 每一种选择:
    	flag = true;    % 标记选择
    	.......         % 选择后处理
    	DFS(step + 1)   % 下一步
    	flag = false    % 移除选择
    	.......         % 恢复现场
    end
end

问题思路

根据上面的模板:

① 选择是什么?

从 2 到正整数 n 都是一种选择

② 如何保证因子互不重复?

使用一个used[i] 数组表示 i 这个数已经使用

③ 要处理什么?

首先当然是used[i]要标记为使用

可以考虑设一个数组numbers[],用来存放因子选择序列,将当前选择push

④ Step + 1具体是什么?

选择完i之后,n 要减去 i,即n -= i,然后进入下一层

⑤ 终止条件是什么?

n == 1返回 1

n == 2时,返回 2 (2 = 1 + 1不行,2 = 0 + 2 直接让乘积归零,所以不分)

n == 3时,返回 2(3 = 1 + 2)

n == 4时,返回 3(4 = 1 + 3)

设置这么多,是因为这些特殊数据很坑

代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

long long maxProduct(int n, vector<bool>& used, vector<int>& numbers) {
    if (n == 1) {
        return 1;
    } else if (n == 2) {
        return 2;
    } else if (n == 3) {
        return 2;
    } else if (n == 4) {
        return 3;
    }

    long long maxproduct = 1;
    for (int i = 2; i <= n; ++i) {
        if (!used[i]) {
            used[i] = true;
            numbers.push_back(i);
            maxproduct = max(maxproduct, i * maxProduct(n - i, used, numbers));
            numbers.pop_back();
            used[i] = false;
        }
    }
    return maxproduct;
}

int main() {
    int n;
    cin >> n;
    vector<bool> used(n + 1, false);
    vector<int> numbers;
    cout << maxProduct(n, used, numbers);

    return 0;
}


二、贪心算法

问题思路

课本提供的思路如下:

① 从 2 作为因子开始,到3、4、……,不断使得因子 + 1

② 到最后剩下一个余数 R 时,会有以下几种情况:

  • 余数 R 比前一个因子小
  • 余数 R = 前一个因子

③ 将余数 R 拆成 R = 1 + 1 + ⋯ + 1 R=1+1+\cdots+1 R=1+1++1,每个 1 赋给前面的所有因子

为什么不全部给某一个因子?

——根据贪心策略,因子越接近,乘积越大。全部给某一个因子将会拉大差距。

  • 余数 R 比前一个因子小,从大到小让因子 + 1
  • 余数 R = 前一个因子,从大到小让因子 + 1,此时剩下一个 1,再赋给前一个因子

为什么要从大到小?

——如果从小到大,因为因子之间差 1 ,一旦赋 1,如果余数 R 不够让全部因子 + 1,就会出现重复

例如 n = 10,因子有 2 3 4,余 1,如果从小到大,则变成3 3 4,出现重复

代码

#include <iostream>
#include <vector>
using namespace std;

int max_X(int n, vector<int>& factorArray) {
    // 初始化因子是 2,remain表示余数,index是数组索引
    int factor = 2, remain = 0, index = 0;
    // 扣去因子 2
    n -= factor;
    // 加入到因子选择序列中
    factorArray.push_back(factor);
    
    // 因子不断 + 1,并加入到选择序列中
    while (n > factorArray[index]) {
        factor++;
        index++;
        factorArray.push_back(factor);
        n -= factor;
        remain = n;
    }
    // 处理余数
    while (remain > 0) {
        if (index < 0)
            break;
        factorArray[index] += 1;
        remain -= 1;
        index--;
    }
    
    int len = factorArray.size();
    long long X = 1;
    // remain = 前一个余数时,最后还剩下 1
    if (remain > 0) {
        factorArray[len - 1]++;
    }
    // 计算乘积
    for (int factor : factorArray) {
        X *= factor;
    }
    
    return X;
}

int main()
{
    int n;
    cin >> n;
    
    if (n == 1) {
        cout << 1;
    } else if (n == 2) {
        cout << 2;
    } else if (n == 3) {
        cout << 2;
    } else if (n == 4) {
        cout << 3;
    } else {
    	vector<int> factorArray;
    	cout << max_X(n, factorArray);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值