题目描述
今年是国际数学联盟确定的"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]=maxt=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. 动态规划实现
三层循环实现:
- 外层循环:乘号数量 jjj 从 111 到 KKK
- 中层循环:数字个数 iii 从 j+1j+1j+1 到 NNN(至少需要 j+1j+1j+1 个数字才能插入 jjj 个乘号)
- 内层循环:最后一个乘号位置 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;
}