4181:【GESP2603七级】拆分

GESP 拆分题------从暴力到数学的三层优化之路


目录

  1. 题目背景与原题呈现

  2. 问题抽象与核心分析

  3. 解法一:暴力深搜(DFS + 回溯)

  4. 解法二:动态规划(DP)优化

  5. 解法三:数学规律最优解

  6. 三种解法对比与总结

  7. 完整代码与提交说明


一、题目背景与原题呈现

这是一道 GESP 七级算法真题,核心考察「整数拆分求最大乘积」问题,也是竞赛中经典的优化类题目。

原题描述

小 A 想将正整数 n 拆分成若干个正整数之和,并最大化拆分后的正整数之积。你需要求出拆分后正整数之积的最大值对 10^9 取模的结果。

形式化地说,n的拆分是满足 $$a_1 + a_2 + ... + a_k = n 的若干个正整数 a_1,a_2,...,a_k(其中 1 < k < n),你需要求所有拆分中 a_1 * a_2 * ... *a_k 的最大值对 10^9 取模的结果。

输入输出

  • 输入:第一行一个正整数t(数据组数);接下来 t行,每行一个正整数 n。

  • 输出:每组数据输出一行,为拆分后乘积最大值对 10^9 取模的结果。

样例

|-----------|----------------|
| 输入 | 输出 |
| 3 5 8 100 | 6 18 755407364 |


二、问题抽象与核心分析

问题抽象

给定正整数 n,将其拆分为若干正整数之和 a_1+a_2+...+a_k=n,求 a_1×a_2×...×a_k 的最大值(结果对 10^9 取模)。

核心矛盾

  • 直接枚举所有拆分组合(暴力法)会随着 n 增大,组合数指数级增长,无法通过大数据用例;

  • 直接找数学规律需要一定的数学推导能力,容易出错;

  • 动态规划作为中间优化方案,兼顾了直观性和效率,是从暴力到最优解的过渡桥梁。


三、解法一:暴力深搜(DFS + 回溯)

思路分析

这是拿到题目时的「第一反应」:既然要找所有拆分组合,那直接用深搜 + 回溯枚举所有合法拆分,计算每个组合的乘积,记录最大值即可。

关键实现细节

  1. 去重优化:通过限制拆分数字非递减(下一个数≥上一个数),避免重复枚举同一拆分的不同排列(如 2+3 和 3+2 视为同一组合);

  2. 取模处理:每次乘积计算后对 10^9 取模,避免数值溢出;

  3. 剪枝优化:剩余和即使全拆为 1 也无法凑够 n 时,直接回溯。

完整代码

复制代码
cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;

const int MOD = 1000000000;
long long maxP;
int n;

void dfs(int sum, long long mul, int last) {
    if (sum == n) {
        maxP = max(maxP, mul);
        return;
    }
    for (int i = last; i <= n - sum; ++i) {
        dfs(sum + i, (mul * i) % MOD, i);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        cin >> n;
        maxP = 0;
        dfs(0, 1, 1);
        cout << maxP % MOD << "\n";
    }
    return 0;
}

优缺点与得分情况

  • ✅ 优点:思路直接,容易理解,无需复杂数学推导;

  • ❌ 缺点:时间复杂度为指数级,仅能通过 n≤20 左右的小数据,n=100 时会直接超时;

  • 得分情况:仅能通过前 2~3 个小数据点,得分率极低。


四、解法二:动态规划(DP)优化

思路分析

暴力法超时的核心问题是重复计算了大量子问题,因此我们可以用动态规划预处理每个数的最优解,将时间复杂度从指数级降到线性级。

状态定义

  • f[i]:将 i 拆分为若干正整数之和时,乘积的最大值(对 10^9 取模);

  • lnf[i]:f[i] 的自然对数,用于比较乘积大小(避免直接计算大数溢出)。

状态转移方程

  • 对于每个 i,最优拆分只有两种可能:拆出一个 2,或拆出一个 3(推导见下文);

  • 转移:

    • 拆 2:lnf[i+2] = lnf[i] + ln2,f[i+2] = (2×f[i])%mod;

    • 拆 3:lnf[i+3] = lnf[i] + ln3,f[i+3] = (3×f[i])%mod;

  • 每次选择对数更大的方案(对数单调递增,对数越大乘积越大)。

完整代码

复制代码
cpp 复制代码
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;

const int N = 1e6 + 5;
const int mod = 1e9;
const double ln2 = log(2);
const double ln3 = log(3);

int f[N];
double lnf[N];

int main() {
    f[0] = f[1] = 1;
    for (int i = 0; i < N; ++i) {
        if (i + 2 < N && lnf[i] + ln2 > lnf[i + 2]) {
            lnf[i + 2] = lnf[i] + ln2;
            f[i + 2] = 2LL * f[i] % mod;
        }
        if (i + 3 < N && lnf[i] + ln3 > lnf[i + 3]) {
            lnf[i + 3] = lnf[i] + ln3;
            f[i + 3] = 3LL * f[i] % mod;
        }
    }
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        scanf("%d", &n);
        printf("%d\n", f[n]);
    }
    return 0;
}

优缺点与得分情况

  • ✅ 优点:时间复杂度 O(n),可处理 n≤10^6 的数据,比暴力法效率大幅提升;

  • ❌ 缺点:需要预处理数组,空间复杂度 O(n),且依赖「拆 2 或拆 3」的隐含规律;

  • 得分情况:可通过所有 n≤10^6 的测试用例,在本题中可拿满分。


五、解法三:数学规律最优解

核心规律推导

通过数学分析,我们可以得出「整数拆分求最大乘积」的黄金法则:

  1. 优先拆成 3:3 的乘积效率最高(3*3>2*2*2 ,3+3=6 乘积为 9,2+2+2=6 乘积为 8);

  2. 余数处理:

    1. 若 n%3=0:全拆为 3,乘积为 3^{n/3};

    2. 若 n%3=1:拆为两个 2 和若干 3(3+1=2+2,2×2>3×1),乘积为 4×3^{(n-4)/3};

    3. 若 n%3=2:拆为一个 2 和若干 3,乘积为 2×3^{n/3};

  3. 特殊情况:n=1 乘积为 1,n=2 乘积为 2,n=3 乘积为 3。

关键实现细节

  • 快速幂:用快速幂计算 3^k%mod,时间复杂度 O(log k),避免循环乘法超时;

  • 取模处理:每次乘法后对 10^9 取模,防止溢出。

完整代码

复制代码
cpp 复制代码
#include <iostream>
using namespace std;

const int MOD = 1000000000;

long long qpow(long long a, long long b) {
    long long res = 1;
    while (b > 0) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        if (n == 1) cout << "1\n";
        else if (n == 2) cout << "2\n";
        else if (n == 3) cout << "3\n";
        else {
            long long ans;
            if (n % 3 == 0) ans = qpow(3, n / 3);
            else if (n % 3 == 1) ans = qpow(3, (n - 4) / 3) * 4 % MOD;
            else ans = qpow(3, n / 3) * 2 % MOD;
            cout << ans << "\n";
        }
    }
    return 0;
}

优缺点与得分情况

  • ✅ 优点:时间复杂度 O(log n),空间复杂度 O(1),可处理 n≤10^{18} 的数据,效率拉满;

  • ❌ 缺点:需要推导数学规律,对数学能力有一定要求;

  • 得分情况:可通过所有测试用例,是本题的最优解。


六、三种解法对比与总结

|------|-------------|------------|------------|--------------|-------------|
| 解法 | 时间复杂度 | 空间复杂度 | 适用场景 | 优点 | 缺点 |
| 暴力深搜 | 指数级 O(2^n) | O(n) (递归栈) | n≤20 | 思路直观,无需推导 | 大数据超时,得分率低 |
| 动态规划 | O(n) | O(n) | n≤10^6 | 线性时间,可通过本题数据 | 需预处理,依赖隐含规律 |
| 数学规律 | O(log n) | O(1) | n≤10^{18} | 效率最高,无额外空间 | 需推导数学规律 |

核心优化思路

这道题的优化路径,正是算法竞赛中常见的「暴力→优化→最优解」的经典路线:

  1. 暴力法是「起点」,帮助我们理解问题的本质;

  2. 动态规划是「过渡」,通过预处理子问题,将指数级复杂度降到线性级;

  3. 数学规律是「终点」,通过数学推导找到最优解,将复杂度降到对数级。


七、完整代码与提交说明

提交建议

  • 本题在竞赛场景下,优先使用数学规律解法,效率最高且无额外空间消耗;

  • 若对数学推导不熟悉,可使用动态规划解法,在本题中同样可拿满分;

  • 暴力法仅适合理解问题,不建议提交。


相关推荐
无忧.芙桃1 小时前
现代C++精讲之处理类型
开发语言·c++
敢敢のwings1 小时前
NVIDIA Thor学习之 |在Jetson AGX Thor上部署OpenClaw并基于Ollama的边缘AI协作实战(二)
人工智能·学习
黎梨梨梨_1 小时前
C++入门基础(下)(重载,引用,inline,nullptr)
开发语言·c++·算法
Jasmine_llq2 小时前
《B4411 [GESP202509 二级] 优美的数字》
算法·暴力枚举算法·逐位校验算法·统一数位判断算法·条件计数算法·自定义函数判断算法
做时间的朋友。2 小时前
小华地图寻宝
算法
贾斯汀玛尔斯2 小时前
每天学一个算法--单调栈(Monotonic Stack)
运维·服务器·算法
ZPC82102 小时前
ROS2 速度远快于 UDP的完整方案(同机节点)
人工智能·算法·计算机视觉·机器人
khalil10202 小时前
代码随想录算法训练营Day-34动态规划03 | 01背包问题 二维、01背包问题 一维、416. 分割等和子集
数据结构·c++·算法·leetcode·动态规划·背包问题·01背包
三分钟管理实战案例2 小时前
华恒智信助力传统制造与科技服务行业完成激活组织效能,打破“躺平”困局
学习