题目描述:
今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰 90 周年。在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好朋友 XZ 也有幸得以参加。活动中,主持人给所有参加活动的选手出了这样一道题目:
设有一个长度为 N 的数字串,要求选手使用 K 个乘号将它分成 K+1 个部分,找出一种分法,使得这 K+1 个部分的乘积能够为最大。
同时,为了帮助选手能够正确理解题意,主持人还举了如下的一个例子:
有一个数字串:312,当N=3,K=1 时会有以下两种分法:
- 3×12=36
- 31×2=62
这时,符合题目要求的结果是:31×2=62。
现在,请你帮助你的好朋友 XZ 设计一个程序,求得正确的答案。
输入格式
程序的输入共有两行:
第一行共有 2个自然数 N,K。
第二行是一个长度为 N 的数字串。
输出格式
结果显示在屏幕上,相对于输入,应输出所求得的最大乘积(一个自然数)。
输入输出样例
输入 #1
4 2
1231
输出 #1
62
数据范围与约定
对于 60%的测试数据满足 6≤N≤20。
对于所有测试数据,6≤N≤40,1≤K≤6。
题意:
题目给出位数为N的整数,现要将该整数“切开”K刀,将K+1个部分(位数>=1)组成整数后相乘,求乘积最大值。
思路:
维护数组dp[i][j],记作前i个数字(左边开始)“切开”j刀的乘积最大值;并开辟数组mp[i][j],记作第i个数字到第j个数字(左边开始)组成的整数;那么对应的转移方程为dp[i][j] = max(dp[i][j], dp[k][j-1]*mp[k+1][j]), k为枚举最后一个因数的起始前一个位置,k=[j,i);最后还因为输入的整数过大,加上高精运算优化一下即可。
dp
我觉得只要能理解这个转移方程这道题就大差不差了,高精只不过时计算数据的工具,我先来解释一下转移方程:dp[i][j] = max(dp[i][j], dp[k-1][j-1]*mp[k][j])
对于dp[i][j],即前i个数(包括i)被切j刀,由于我们时从i=0,j=0遍历过来的,即任意i0(i0<i), 对于的dp[i0][j-1]都是已经计算好的,所以我们只要去枚举最后一个乘法的位置,换句话说就是去枚举最后一个因数last,那么乘积就是dp[i0][j-1]*mp[i0+1][i],在枚举所以最后一个因数last后取最大的dp[i0][j-1]*mp[i0+1][i]就是我们要求的dp[i][j];
上图是以dp[6][3]为例,其中dp[1][2],dp[2][2]无意义,因为“切的刀数”大于等于数的位数;我们在遍历枚举的时候可以将其continue掉;
首先是先初始化mp数组:
// 获取(左边)第i位到第j位组合的整数值;
int get_nums(int i, int j){
if(i == j) return num[i];
int ret = 0;
for(;i <= j;i++) ret = ret * 10 + num[i];
return ret;
}
// mp;
for(int i = 1;i <= N;i++){
for(int j = i; j <= N;j++){
mp[i][j] = get_nums(i, j);
}
}
再维护dp数组,其中k变量即为上图中i0,为了每次枚举有意义,故从j处开始遍历枚举(解释一下为什么从j开始遍历,因为你要dp有意义,那么k要大于“刀数”j-1,所以从j开始):
// dp;
memset(dp, 0, sizeof(dp));
for(int i = 1;i <= N;i++){
for(int j = 0;j <= 6;j++){ // 乘法个数j;
if(i <= j) break; // 乘法个数不能>=因数;
if(j == 0) {dp[i][j] = mp[1][i]; continue;}
for(int k = j;k < i;k++){
int last = mp[k+1][i]; // 最后一个因数;
dp[i][j] = max(dp[i][j], dp[k][j-1]*last);
}
}
}
到这里去提交代码就有60分了,还差40分就是因为整数太大爆了,就算是long long也接不住10^39这么大的数,所以这里就要用高精度运算去解决这个问题,下面先给出完整的无高精dp做法源码:
60分源码
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define int long long
#define endl '\n'
int N, K;
int dp[50][10] = {0}, mp[50][50], num[50]; // dp[i][j] 在第1位(右边)到第i位k=j时的乘积最大值;
// 获取(左边)第i位到第j位组合的整数值;
int get_nums(int i, int j){
if(i == j) return num[i];
int ret = 0;
for(;i <= j;i++) ret = ret * 10 + num[i];
return ret;
}
// 初始化;
void Init(){
cin >> N >> K;
// num;
for(int i = 1;i <= N;i++){
char ch; cin >> ch;
num[i] = ch - '0';
}
// mp;
for(int i = 1;i <= N;i++){
for(int j = i; j <= N;j++){
mp[i][j] = get_nums(i, j);
}
}
// dp;
memset(dp, 0, sizeof(dp));
for(int i = 1;i <= N;i++){
for(int j = 0;j <= 6;j++){ // 乘法个数j;
if(i <= j) break; // 乘法个数不能>=因数;
if(j == 0) {dp[i][j] = mp[1][i]; continue;}
for(int k = j;k < i;k++){
int last = mp[k+1][i]; // 最后一个因数;
dp[i][j] = max(dp[i][j], dp[k][j-1]*last);
}
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
Init();
cout << dp[N][K];
return 0;
}
高精+dp
高精度运算其实就是把int,double等这些数据类型用字符串类型string代替,再模拟加减乘除来解决int,double等类型精度不够的问题;所以这里需要将dp和mp的数据类型int转为string类型,再模拟乘法运算即可解决数据过大的问题。
首先是对mp数组初始化的修改:
string mp[50][50], dp[50][50]; // int -> string;
获取(左边)第i位到第j位组合的整数值;
//int get_nums(int i, int j){
// if(i == j) return num[i];
// int ret = 0;
// for(;i <= j;i++) ret = ret * 10 + num[i];
// return ret;
//}
// 获取(左边)第i位到第j位组合的整数值;
string get_nums(int i, int j){
string ret = "";
if(i == j) return ret = num[i];
for(;i <= j;i++) ret += num[i];
return ret;
}
然后就是修改dp过程,这里就要用到高精度乘法(其实就是字符串“10”和“11”模拟乘法最后求得字符串“110”的过程):
// 返回较大值;
string maxn(string a, string b){
if(a.length() != b.length()) return a.length() > b.length() ? a : b;
return a > b ? a : b;
}
// 高精度乘法;
string mul(string a, string b){
// string -> int;
int len1 = a.length(), len2 = b.length(), len3 = 0;
int a1[1010], b1[1010], c1[1010]; // 将string a,b的每一位数用int数组保存,用于模拟计算;
memset(a1, 0, sizeof(a1));
memset(b1, 0, sizeof(b1));
memset(c1, 0, sizeof(c1));
for(int i = 1;i <= len1;i++) a1[i] = a[len1-i] - '0'; // 为了进位方便,将整数倒转过来;
for(int i = 1;i <= len2;i++) b1[i] = b[len2-i] - '0';
// 相乘;
for(int i = 1;i <= len2;i++){ // 取一位乘数;
int in = 0;
for(int j = 1;j <= len1;j++){ // 取一位被乘数;
c1[i+j-1] += a1[j] * b1[i] + in; // 第i位与第j位相乘是保存在i+j-1为上,模拟一下小学乘法计算就可以发现规律;
in = c1[i+j-1] / 10; // 更新进位;
c1[i+j-1] %= 10; // 剩余;
}
c1[i+len1] = in; // 处理最后进位;
}
len3 = len2 + len1;
while(c1[len3] == 0 && len3 > 1) len3--; // 取消前置0且保证结果为0时保留一个0;
// int -> string;
string c = "";
for(int i = len3;i >= 1;i--) c += '0' + c1[i];
return c;
}
下面就是dp过程的改动:
dp;
//memset(dp, 0, sizeof(dp));
//for(int i = 1;i <= N;i++){
// for(int j = 0;j <= 6;j++){ // 乘法个数j;
// if(i <= j) break; // 乘法个数不能>=因数;
// if(j == 0) {dp[i][j] = mp[1][i]; continue;}
// for(int k = j;k < i;k++){
// int last = mp[k+1][i]; // 最后一个因数;
// dp[i][j] = max(dp[i][j], dp[k][j-1]*last);
// }
// }
//}
// dp;
for(int i = 1;i <= N+10;i++) for(int j = 1;j <= 6+10;j++) dp[i][j] = "0";
for(int i = 1;i <= N;i++){
for(int j = 0;j <= 6;j++){ // 乘法个数j;
if(i <= j) break; // 乘法个数不能>=因数;
if(j == 0) {dp[i][j] = mp[1][i]; continue;}
for(int k = j;k < i;k++){
string last = mp[k+1][i]; // 最后一个因数;
dp[i][j] = maxn(dp[i][j], mul(dp[k][j-1], last));
}
}
}
好的到这里我们就实现了高精度优化dp了,下面给出AC源码:
AC源码
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define int long long
#define endl '\n'
int N, K;
string num;
string mp[50][50], dp[50][50];
// 获取(左边)第i位到第j位组合的整数值;
string get_nums(int i, int j){
string ret = "";
if(i == j) return ret = num[i];
for(;i <= j;i++) ret += num[i];
return ret;
}
// 返回较大值;
string maxn(string a, string b){
if(a.length() != b.length()) return a.length() > b.length() ? a : b;
return a > b ? a : b;
}
// 高精度乘法;
string mul(string a, string b){
// string -> int;
int len1 = a.length(), len2 = b.length(), len3 = 0;
int a1[1010], b1[1010], c1[1010];
memset(a1, 0, sizeof(a1));
memset(b1, 0, sizeof(b1));
memset(c1, 0, sizeof(c1));
for(int i = 1;i <= len1;i++) a1[i] = a[len1-i] - '0'; // 为了进位方便,将整数倒转过来;
for(int i = 1;i <= len2;i++) b1[i] = b[len2-i] - '0';
// 相乘;
for(int i = 1;i <= len2;i++){ // 取一位乘数;
int in = 0;
for(int j = 1;j <= len1;j++){ // 取一位被乘数;
c1[i+j-1] += a1[j] * b1[i] + in;
in = c1[i+j-1] / 10; // 更新进位;
c1[i+j-1] %= 10; // 剩余;
}
c1[i+len1] = in; // 处理最后进位;
}
len3 = len2 + len1;
while(c1[len3] == 0 && len3 > 1) len3--; // 取消前置0且保证结果为0时保留一个0;
// int -> string;
string c = "";
for(int i = len3;i >= 1;i--) c += '0' + c1[i];
return c;
}
void Init(){
cin >> N >> K >> num;
// mp;
for(int i = 1;i <= N;i++){
for(int j = i; j <= N;j++){
mp[i][j] = get_nums(i-1, j-1);
}
}
// dp;
for(int i = 1;i <= N+10;i++) for(int j = 1;j <= 6+10;j++) dp[i][j] = "0";
for(int i = 1;i <= N;i++){
for(int j = 0;j <= 6;j++){ // 乘法个数j;
if(i <= j) break; // 乘法个数不能>=因数;
if(j == 0) {dp[i][j] = mp[1][i]; continue;}
for(int k = j;k < i;k++){
string last = mp[k+1][i]; // 最后一个因数;
dp[i][j] = maxn(dp[i][j], mul(dp[k][j-1], last));
}
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
Init();
cout << dp[N][K];
return 0;
}
好了这道题就到这啦,感谢观看到这里 ~\(≧▽≦)/~,还得去肝高精除法了\( ̄ー ̄) /,各位大佬看得高兴就给caikun点赞鼓励一下吧(•̀⌄•́)。