【信息学奥赛一本通】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. 状态定义

定义状态 dpijdpijdpij

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

2. 预处理

为了快速获取任意连续子串的数值,需要预处理数组 numlrnumlrnumlr

  • numlrnumlrnumlr 表示从第 lll 位到第 rrr 位(包含)组成的整数
  • 计算方法:numlr=numlr−1×10+arnumlr = numlr-1 \times 10 + arnumlr=numlr−1×10+ar

示例 :数字串 "123112311231"

  • num12=12num12 = 12num12=12
  • num24=231num24 = 231num24=231

3. 状态初始化

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

  • dpi0=num1idpi0 = num1idpi0=num1i(整个前 iii 位数字)
  • 即:前 iii 个数字不分割,就是它本身的值

4. 状态转移方程

对于 dpijdpijdpij,我们考虑最后一个乘号的位置:

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

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

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

解释

  • 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. 最终答案

所求的最大乘积为:
答案=dpNK \text{答案} = dpNK 答案=dpNK

算法复杂度

  • 时间复杂度 :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;
}
相关推荐
凌波粒7 分钟前
LeetCode--108.将有序数组转换为二叉搜索树(二叉树)
算法·leetcode·职场和发展
liulilittle7 分钟前
KCC:在 BBR 思路上的一次探索
网络·tcp/ip·算法·bbr·通信·拥塞控制·kcc
浦信仿真大讲堂29 分钟前
达索系统SIMULIA Abaqus 2026接触和约束的增强新功能介绍
人工智能·python·算法·仿真软件·达索软件
点云侠39 分钟前
PCL 生成三棱锥点云
c++·算法·最小二乘法
兰令水1 小时前
leecodecode【面试150】【2026.6.13打卡-java版本】
java·算法·leetcode
临沂堇1 小时前
刷题日志 | Leetcode Hot 100 哈希
算法·leetcode·哈希算法
.道阻且长.1 小时前
C++ string 操作指南:接口解析
java·c语言·开发语言·c++
玉小格1 小时前
一次关于Python的总结
算法
伊甸31 小时前
从企业级项目学敏感词过滤:DFA算法与双层缓存实战
java·算法·缓存
laplaya1 小时前
使用 vcpkg 管理 C++ 项目中的依赖
开发语言·c++