【信息学奥赛一本通】1275:【例9.19】乘积最大

题目描述

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

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

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

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

  • 3∗12=363*12=363∗12=36

  • 31∗2=6231*2=6231∗2=62

这时,符合题目要求的结果是:31∗2=6231*2=6231∗2=62。

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

输入格式

程序的输入共有两行:

第一行共有 222 个自然数 N,KN,KN,K。

第二行是一个长度为 NNN 的数字串。

输出格式

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

样例

输入

复制代码
4 2
1231

输出

复制代码
62

说明/提示

数据范围与约定

  • 6≤N≤106≤N≤106≤N≤10,1≤K≤61≤K≤61≤K≤6

解题思路

本题采用动态规划的解法,通过状态转移逐步构建最优解。

1. 状态定义

定义状态 dp[i][j]dp[i][j]dp[i][j]:

  • 含义 :在前 iii 个数字中插入 jjj 个乘号能得到的最大乘积
  • iii:前 iii 个数字(1 ≤ i ≤ N)
  • jjj:插入的乘号数量(0 ≤ j ≤ K)

2. 预处理

为了快速获取任意连续子串的数值,需要预处理数组 num[l][r]num[l][r]num[l][r]:

  • num[l][r]num[l][r]num[l][r] 表示从第 lll 位到第 rrr 位(包含)组成的整数
  • 计算方法:num[l][r]=num[l][r−1]×10+a[r]num[l][r] = num[l][r-1] \times 10 + a[r]num[l][r]=num[l][r−1]×10+a[r]

示例 :数字串 "123112311231"

  • num[1][2]=12num[1][2] = 12num[1][2]=12
  • num[2][4]=231num[2][4] = 231num[2][4]=231

3. 状态初始化

当没有乘号时(j=0j=0j=0):

  • dp[i][0]=num[1][i]dp[i][0] = num[1][i]dp[i][0]=num[1][i](整个前 iii 位数字)
  • 即:前 iii 个数字不分割,就是它本身的值

4. 状态转移方程

对于 dp[i][j]dp[i][j]dp[i][j],我们考虑最后一个乘号的位置:

设最后一个乘号放在第 ttt 个数字之后(j≤t<ij ≤ t < ij≤t<i),则:

  • 前 ttt 个数字中插入了 j−1j-1j−1 个乘号,最大乘积为 dp[t][j−1]dp[t][j-1]dp[t][j−1]
  • 最后一段是从第 t+1t+1t+1 位到第 iii 位,数值为 num[t+1][i]num[t+1][i]num[t+1][i]

因此状态转移方程为:
dp[i][j]=max⁡t=ji−1{dp[t][j−1]×num[t+1][i]} dp[i][j] = \max_{t=j}^{i-1} \{ dp[t][j-1] \times num[t+1][i] \} dp[i][j]=t=jmaxi−1{dp[t][j−1]×num[t+1][i]}

解释

  • ttt 必须至少为 jjj:前 ttt 个数字要插入 j−1j-1j−1 个乘号,至少需要 jjj 个数字
  • ttt 必须小于 iii:最后一个乘号必须在第 iii 个数字之前

5. 动态规划实现

三层循环实现:

  1. 外层循环:乘号数量 jjj 从 111 到 KKK
  2. 中层循环:数字个数 iii 从 j+1j+1j+1 到 NNN(至少需要 j+1j+1j+1 个数字才能插入 jjj 个乘号)
  3. 内层循环:最后一个乘号位置 ttt 从 jjj 到 i−1i-1i−1

6. 最终答案

所求的最大乘积为:
答案=dp[N][K] \text{答案} = dp[N][K] 答案=dp[N][K]

算法复杂度

  • 时间复杂度 :O(N2K)O(N^2K)O(N2K)
    • 预处理:O(N2)O(N^2)O(N2)
    • DPDPDP 过程:三层循环 O(N2K)O(N^2K)O(N2K)
    • 由于 N≤10,K≤6N≤10, K≤6N≤10,K≤6,完全可行
  • 空间复杂度 :O(N2+NK)O(N^2 + NK)O(N2+NK)

参考代码

简洁版

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 15, K = 10;
ll num[N][N], dp[N][K];
int n, k, a[N];
string s;

void creat_num(){
	for(int i = 0; i < s.size(); i ++)	a[i + 1] = s[i] - '0';
	
	for(int i = 1; i <= n; i ++)
		for(int j = i; j <= n; j ++)
			if(i == j)	num[i][j] = a[i];
			else	num[i][j] = num[i][j - 1] * 10 + a[j];
}

int main(){
	cin >> n >> k >> s;
    creat_num();
    for(int i = 1; i <= n ; i ++)	dp[i][0] = num[1][i]; 
    for(int i = 1; i <= n ; i ++)
    	for(int j = 1; j <= min(i - 1, k); j ++)
    		for(int kk = j; kk < i; kk ++)
	    		dp[i][j] = max(dp[i][j], dp[kk][j - 1] * num[kk + 1][i]);
	cout << dp[n][k];
	return 0;
}

注释版

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;  // 定义long long类型别名

const int N = 15, K = 10;

// 定义数组:
// num[i][j]:存储从第i位到第j位(包含)组成的数字
// dp[i][j]:表示前i个数字中插入j个乘号的最大乘积
ll num[N][N], dp[N][K];

// 变量定义:
// n:数字串长度
// k:乘号数量
// a[]:将字符串转换为数字数组存储
int n, k, a[N];
string s;  // 输入的数字串

// 预处理函数:生成num数组
void creat_num(){
    // 将字符串转换为数字数组(1-based索引)
    for(int i = 0; i < s.size(); i ++)
        a[i + 1] = s[i] - '0';
    
    // 计算所有连续子串的数值
    for(int i = 1; i <= n; i ++)
        for(int j = i; j <= n; j ++)
            if(i == j) 
                num[i][j] = a[i];  // 单个数字
            else 
                // 利用前一个结果计算当前区间数值
                // 例如:num[1][3] = num[1][2] * 10 + a[3]
                num[i][j] = num[i][j - 1] * 10 + a[j];
}

int main(){
    // 输入数据
    cin >> n >> k >> s;
    
    // 预处理,生成num数组
    creat_num();
    
    // 初始化DP数组:没有乘号的情况
    // 前i个数字没有乘号时,就是整个前i位数字的值
    for(int i = 1; i <= n ; i ++)
        dp[i][0] = num[1][i]; 
    
    // 动态规划主过程
    for(int i = 1; i <= n ; i ++)           // i:前i个数字
        for(int j = 1; j <= min(i - 1, k); j ++)  // j:插入j个乘号(j不超过i-1和k)
            for(int kk = j; kk < i; kk ++)  // kk:最后一个乘号在第kk个数字之后
                // 状态转移:
                // 前i个数字插入j个乘号的最大值 =
                // max{ 前kk个数字插入j-1个乘号的最大值 × 第kk+1到第i位数字 }
                dp[i][j] = max(dp[i][j], dp[kk][j - 1] * num[kk + 1][i]);
    
    // 输出结果:前n个数字插入k个乘号的最大乘积
    cout << dp[n][k];
    return 0;
}
相关推荐
coder攻城狮2 小时前
VTK系列1:在屏幕绘制多边形
c++·3d
Daydream.V2 小时前
逻辑回归实例问题解决(LogisticRegression)
算法·机器学习·逻辑回归
代码无bug抓狂人2 小时前
C语言之表达式括号匹配
c语言·开发语言·算法
不穿格子的程序员2 小时前
从零开始写算法——普通数组篇:缺失的第一个正数
算法·leetcode·哈希算法
Nebula_g2 小时前
线程进阶: 无人机自动防空平台开发教程(更新)
java·开发语言·数据结构·学习·算法·无人机
HAPPY酷2 小时前
构造与析构:C++ 中对象的温柔生灭
java·jvm·c++
又见野草2 小时前
C++类和对象(下)
开发语言·c++
rit84324992 小时前
基于MATLAB的环境障碍模型构建与蚁群算法路径规划实现
开发语言·算法·matlab
hoiii1872 小时前
MATLAB SGM(半全局匹配)算法实现
前端·算法·matlab