题解:洛谷 P1018 乘积最大

【题目来源】

洛谷:P1018 [NOIP 2000 提高组] 乘积最大

【题目描述】

今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰 90 周年。在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好朋友 XZ 也有幸得以参加。活动中,主持人给所有参加活动的选手出了这样一道题目:

设有一个长度为 \(N\) 的数字串,要求选手使用 \(K\) 个乘号将它分成 \(K+1\) 个部分,找出一种分法,使得这 \(K+1\) 个部分的乘积能够为最大。

同时,为了帮助选手能够正确理解题意,主持人还举了如下的一个例子:

有一个数字串:\(312\),当 \(N=3,K=1\) 时会有以下两种分法:

  1. \(3 \times 12=36\)
  2. \(31 \times 2=62\)

这时,符合题目要求的结果是:\(31 \times 2 = 62\)

现在,请你帮助你的好朋友 XZ 设计一个程序,求得正确的答案。

【输入】

程序的输入共有两行:

第一行共有 \(2\) 个自然数 \(N,K\)

第二行是一个长度为 \(N\) 的数字串。

【输出】

结果显示在屏幕上,相对于输入,应输出所求得的最大乘积(一个自然数)。

【输入样例】

4 2
1231

【输出样例】

62

【核心思想】

  1. 问题分析:给定一个长度为 \(N\) 的数字串,要求在其中插入 \(K\) 个乘号,将数字串分成 \(K+1\) 个部分,使得这些部分的乘积最大。数字串长度 \(N\) 可达 40,结果可能非常大,远超 64 位整数范围,因此必须使用高精度计算。本质是区间 DP + 高精度,通过枚举最后一个乘号的位置进行状态转移。

  2. 算法选择

    • 区间 DP + 高精度:设 \(dp[i][j]\) 表示前 \(i\) 个数字中插入 \(j\) 个乘号所能得到的最大乘积。预处理 \(num[l][r]\) 为数字串第 \(l\)\(r\) 位组成的整数(高精度)。转移时枚举最后一个乘号的位置 \(p\)\(j \le p < i\)),则有 \(dp[i][j] = \max(dp[p][j-1] \times num[p+1][i])\)
    • DFS 搜索(非最优):代码中还给出了 DFS 枚举所有乘号位置的解法,但存在大量重复计算,仅适合小数据。最优解是 DP。
    • 高精度实现:由于乘积可能极大(\(10^{40}\) 级别),需自定义高精度结构体,支持乘法、比较和输出。
  3. 关键步骤(基于 DP + 高精度):

    • 读入与预处理:读入 \(N, K\) 和数字串 \(s\)(1-based)。预处理 \(num[i][j]\) 为子串 \(s[i..j]\) 对应的整数(高精度)。
    • 初始化 DP\(dp[i][0] = num[1][i]\)(不插入乘号时,前 \(i\) 个数字组成一个数)。
    • DP 转移:枚举乘号个数 \(j\) 从 1 到 \(K\),枚举前 \(i\) 个数字(\(i \ge j+1\)),枚举最后一个乘号位置 \(p\)\(j \le p \le i-1\)),更新 \(dp[i][j] = \max(dp[i][j], dp[p][j-1] \times num[p+1][i])\)
    • 输出答案\(dp[N][K]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(K \cdot N^2 \cdot H)\),其中 \(H\) 为高精度乘法复杂度(位数可达 40),\(N \le 40\)\(K \le 6\)(原题范围较小),完全可行。
    • 空间复杂度:\(O(N^2 \cdot H)\),存储所有子串的高精度值。
  5. 区间 DP + 高精度的核心思想

    • 最优子结构:前 \(i\) 个数字插入 \(j\) 个乘号的最大值,可由前 \(p\) 个数字插入 \(j-1\) 个乘号的最大值乘上剩余部分得到。枚举 \(p\) 即可覆盖所有分割方式。
    • 乘号位置枚举:最后一个乘号的位置 \(p\) 是关键,它将问题分解为“前半部分的最优值”与“后半部分的数值”的乘积。
    • 高精度必要性\(N\) 最大 40,乘积可能达到 \(10^{40}\),远超 long long(约 \(9\times10^{18}\)),必须使用高精度。
    • 适用场景:适用于数字串分割求最大乘积的问题,特别是结果超出基本整数类型的情况。

【算法标签】

普及+ #DFS-一维

【代码详解】

// 60分版本
#include <bits/stdc++.h>
using namespace std;

const int N = 45, K = 8;    // N: 数字串最大长度,K: 乘号最大数量
char s1[N];                 // s1: 存储输入的数字字符串(下标从1开始)
long long f[N][K];          // f[i][j]: 前i个数字中插入j个乘号能得到的最大乘积
long long a[N][N];          // a[i][j]: 数字串第i到第j位组成的整数值
int n, m;                   // n: 数字串长度,m: 乘号个数

int main()
{
    cin >> n >> m;          // 输入长度和乘号数

    // 输入数字串,存储到s1[1] ~ s1[n]
    for (int i = 1; i <= n; i++)
    {
        cin >> s1[i];
    }

    // 预处理所有子串对应的整数值
    // a[i][j] 表示 s1[i..j] 组成的整数
    for (int i = 1; i <= n; i++)
    {
        long long te = 0;
        for (int j = i; j <= n; j++)
        {
            te = te * 10 + (s1[j] - '0');   // 逐步构造子串数值
            a[i][j] = te;
        }
    }

    // 初始化:不插入任何乘号时,前i个数字的乘积就是整个数字串 a[1][i]
    for (int i = 1; i <= n; i++)
        f[i][0] = a[1][i];

    // 动态规划:逐步增加乘号数量
    for (int j = 1; j <= m; j++)                    // j: 乘号个数
    {
        for (int i = j + 1; i <= n; i++)            // i: 前i个数字(至少需要j+1个数字才能放j个乘号)
        {
            // 枚举最后一个乘号的位置k
            // 最后一个乘号放在第k个数字之后,则前k个数字中有j-1个乘号
            for (int k = j; k <= i - 1; k++)
            {
                // 状态转移:前k个数字插入j-1个乘号的最大值 × 剩余部分(k+1..i)组成的数
                f[i][j] = max(f[i][j], f[k][j - 1] * a[k + 1][i]);
            }
        }
    }

    // 输出前n个数字插入m个乘号的最大乘积
    cout << f[n][m] << endl;

    return 0;
}
// 60分版本
#include <bits/stdc++.h>
using namespace std;

#define int long long          // 使用 long long 类型来存储乘积,适合本题范围

int n, k, ans;                 // n: 数字串长度, k: 乘号个数, ans: 最大乘积
string s;                      // 存储输入的数字串

// 深度优先搜索枚举所有乘号位置
// x: 当前要分割的起始下标 (0-based)
// y: 剩余要放置的乘号个数
// z: 当前已经乘得的部分乘积
void dfs(int x, int y, int z)
{
    if (y == 0)                // 没有剩余乘号,处理最后一段
    {
        // 从 x 到末尾的子串转换为整数
        int t = stoll(s.substr(x, n - x));
        t *= z;                // 乘上之前的累积乘积
        ans = max(ans, t);     // 更新最大值
        return;
    }

    // 枚举当前这一段结束的位置 i (包含 i 这一位)
    // 要保证剩余部分能放下 y 个乘号,所以 i + y < n
    for (int i = x; i + y < n; i++)
    {
        // 取出当前段子串并转为整数
        int t = stoll(s.substr(x, i - x + 1));
        t *= z;                // 累乘到当前乘积
        dfs(i + 1, y - 1, t);  // 递归处理后续部分
    }
}

signed main()
{
    cin >> n >> k >> s;        // 输入长度、乘号数和数字串
    dfs(0, k, 1);              // 从第0位开始,放置k个乘号,初始乘积为1
    cout << ans << endl;       // 输出最大乘积
    return 0;
}
// AC版本
#include <bits/stdc++.h>
using namespace std;

int n, k;                   // n: 数字串长度, k: 乘号个数
string s;                   // 存储输入的数字串

// 高精度大整数结构体
struct BIG
{
    int len, num[1005];     // len: 位数, num[1..len] 存储数字, 低位在前

    BIG()                   // 构造函数, 初始化为0
    {
        memset(num, 0, sizeof(num));
        len = 1;
    }

    void set(int n)         // 将整数转换为高精度数
    {
        memset(num, 0, sizeof(num));
        len = 0;
        while (n > 0)
        {
            num[++len] = n % 10;
            n /= 10;
        }
        if (len == 0)       // 如果 n=0, 则长度为1, 数字为0
        {
            len = 1;
        }
    }

    void set(string s)      // 将数字字符串转换为高精度数, 下标从1开始, 低位在前
    {
        memset(num, 0, sizeof(num));
        len = s.size();
        for (int i = 1; i <= len; i++)
        {
            num[i] = s[len - i] - '0';  // 倒序存储
        }
    }

    void print()            // 输出高精度数
    {
        for (int i = len; i >= 1; i--)
        {
            cout << num[i];
        }
        cout << endl;
    }
};

BIG ans;                    // 存储最终最大乘积

// 高精度乘法运算符重载
BIG operator*(BIG a, BIG b)
{
    BIG c;
    c.len = a.len + b.len;          // 乘积的最大可能位数
    for (int i = 1; i <= a.len; i++)
        for (int j = 1; j <= b.len; j++)
            c.num[i + j - 1] += a.num[i] * b.num[j];

    // 统一处理进位
    for (int i = 1; i < c.len; i++)
    {
        c.num[i + 1] += c.num[i] / 10;
        c.num[i] %= 10;
    }
    // 去除前导零
    while (c.len > 1 && c.num[c.len] == 0)
        c.len--;
    return c;
}

// 高精度小于比较运算符, 用于更新最大值
bool operator<(BIG a, BIG b)
{
    if (a.len != b.len) return a.len < b.len;
    for (int i = a.len; i >= 1; i--)
        if (a.num[i] != b.num[i])
            return a.num[i] < b.num[i];
    return false;
}

// 深度优先搜索枚举所有乘号位置
// x: 当前要分割的起始下标 (0-based)
// y: 剩余要放置的乘号个数
// z: 当前已经乘得的部分乘积 (高精度)
void dfs(int x, int y, BIG z)
{
    if (y == 0)     // 没有剩余乘号, 处理最后一段
    {
        BIG t;
        t.set(s.substr(x, n - x));  // 从 x 到末尾的子串
        t = t * z;                  // 乘上之前的累积乘积
        if (ans < t)                // 更新最大值
            ans = t;
        return;
    }

    // 枚举当前这一段结束的位置 i (包含 i 这一位)
    // 要保证剩余部分能放下 y 个乘号, 所以 i + y < n
    for (int i = x; i + y < n; i++)
    {
        BIG t;
        t.set(s.substr(x, i - x + 1));  // 当前段子串
        t = t * z;                      // 累乘
        dfs(i + 1, y - 1, t);           // 递归处理后续部分
    }
}

int main()
{
    cin >> n >> k >> s;
    BIG one;
    one.set(1);             // 初始乘积为1
    dfs(0, k, one);         // 从第0位开始, 放置k个乘号
    ans.print();            // 输出最大乘积
    return 0;
}

【运行结果】

4 2
1231
62
posted @ 2026-08-31 09:22  团爸讲算法  阅读(16)  评论(0)    收藏  举报