动态规划——最大K乘积问题

394 阅读1分钟

问题引入

设 I 是一个 n 位十进制整数。如果将 I 划分为 k 段,则可得到 k 个整数。这 k 个整数的乘积称为 I 的一个 k 乘积。设计一个算法,对于给定的 I 和 k ,求出 I 的最大 k乘积 。

例如十进制整数 12345 划分为 3 段可有如下情形:




算法分析






代码实现

支持负数和打印段

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

int i, j, d, n, k, ans;
int dp[100][100], num[100][100];
int path[100][100];

string str; // 在考虑负数的条件下,字符串比字符数组更好用

void show(int n, int k)
{
    if (k > 0)
    {
        show(path[n][k], k - 1);
        printf("[%d...%d]", path[n][k] + 1, n);
    }
}

int fun()
{
    for (i = 1; i <= n; i++)
    {
        for (j = i; j <= n; j++)
        {
            num[i][j] = num[i][j - 1] * 10 + str[j - 1] - '0';
        }
        dp[i][1] = num[1][i]; // 解决了仅分为一段的情况   (因为当总长度为1时只能分为一段,所以还解决了总长度为1时的所有分段可能)
    }

    for (i = 2; i <= n; i++) // 开始解决总长度大于等于2的情况
    {
        for (j = 2; j <= k; j++) // 开始解决总长度大于等于2,且段数也大于等于2的情况
        {
            dp[i][j] = -1;
            for (d = 1; d < i; d++)
            {                                            // 不断调整当前段前面几部分,再与末尾段相乘
                int temp = dp[d][j - 1] * num[d + 1][i]; /// dp[i][j]代表长度为i段数为j的最大乘积;num[i][j]代表第i位数到第j位数组成的数字
                if (dp[i][j] < temp)
                {
                    dp[i][j] = temp;
                    path[i][j] = d;
                }
            }
        }
    }
    return dp[n][k];
}

int main()
{
    cin >> n >> k;
    cin >> str;

    if (str[0] == '-') /// 负数情况
    {
        ans = 1;
        str.erase(0, 1); // 字符串删掉最开始的符号,后面的字符依次前移
        for (i = 0; i < n; i++)
            ans *= (str[i] - '0'); // 为负数时,每一位都作为一段,就可获得最大乘积值
        cout << -1 * ans << endl;
        show(n, k); // 输出具体的分段方案
        return 0;
    }
    else /// 正数情况
    {
        ans = fun();
        cout << ans << endl;
        show(n, k); // 输出具体的分段方案
        return 0;
    }
}

/*
4 2
1231


5 2
12345
输出:6170



*/


不支持负数

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

int i, j, d, n, k, ans;
int dp[100][100], num[100][100];
char c[100];

int path[100][100];

void show(int n, int k)
{
    if (k > 0)
    {
        show(path[n][k], k - 1);
        printf("[%d...%d]", path[n][k] + 1, n);
    }
}

int main()
{
    cin >> n >> k;
    for (i = 1; i <= n; i++)
        cin >> c[i];

    for (i = 1; i <= n; i++)
    {
        for (j = i; j <= n; j++)
        {
            num[i][j] = num[i][j - 1] * 10 + (c[j] - '0');
        }
        dp[i][1] = num[1][i]; // 解决了任意长度下仅分为一段的情况   (因为当总长度为1时只能分为一段,所以还解决了总长度为1时的所有分段可能)
    }

    for (i = 2; i <= n; i++) // 开始解决总长度大于等于2的情况
    {
        for (j = 2; j <= k; j++) // 开始解决总长度大于等于2,且段数也大于等于2的情况
        {
            dp[i][j] = -1;
            for (d = 1; d < i; d++)
            {                                            // 不断调整当前段前面几部分,再与末尾段相乘
                int temp = dp[d][j - 1] * num[d + 1][i]; /// dp[i][j]代表长度为i段数为j的最大乘积;num[i][j]代表第i位数到第j位数组成的数字
                if (dp[i][j] < temp)
                {
                    dp[i][j] = temp;
                    path[i][j] = d;
                }
            }
        }
    }
    cout << dp[n][k] << endl;
    show(n, k); // 输出具体的分段方案
}

/*
4 2
1231


5 2
12345
输出:6170


*/




运行结果








复杂度分析