问题描述
今年是国际数学联盟确定的“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(6≤N≤40,1≤K≤6)
第二行是一个长度为N的数字串。
输出格式
样例输入
4 2
1231
样例输出
题解:很明显是一道dp的题目,而且看数据规模n==[6,40], k==[1,6];如果是dp的话3维的都没什么问题。注意,为了方便分析我们先将字符串的起始位置设为1
思路如下:构造dp[i][j]数组其中i代表第几个乘号,j表示字符串的位置。具体dp[i][j]的意义就是在第j位置加上后,在[1,j-1](就是左闭右开[1,j))段字符串中乘机的最大值是多少。 有点拗口,自己体会一下。
然后我们就可以用推出dp的递推公式
dp[i][j]=max(dp[i][j],dp[i−1][k])i<=k<=n−(k−(i−1))+1
注意了!可以看到上面k的范围很是诡异,这也是我们需要注意的地方,假定我们按照上面的规则向字符串中放置乘号,假设总共要放k个乘号,现在要放置第i个乘号,那么是不是这个称号之前还要放m-1个乘号,在它之后还要放k-i个乘号?为了保证乘号是有意义的,那么两个乘号之间至少存在一位,又根据上面右闭又开的规则,我们知道起点是(m-1)+1 .注意插一句!现在说为什么要选择左闭右开,这样就能保证在最后一个乘号插入后右边一定有数字保证合法性。 现在继续说终点,右端点的最大位置选择是n-(k-m)+1,这个+1就是看的左闭右开的规则的。 请好好自己推算一下!否则很容易乱掉
下main分享我的代码,注意我的代码中dp的起始是从1开始的,而字符串启示是从0开始的,大概的讲解我已经写在注释中了
#include<bits/stdc++.h>
using namespace std;
int n,k;
long long dp[41][7]={0};
string s="";
int get(int start,int end){ //将串转换成数值
int ans=0;
for(int i=start;i<=end;i++){
ans=ans*10+(s[i-1]-'0');
}
return ans;
}
int main(){
cin>>n>>k;
cin>>s;
for(int i=1+1;i<=n-(k-1)+1;i++){ //初始化注意刚刚博客中说的循环的开始和终止的位置
dp[1][i]=get(1,i-1);
}
for(int i=2;i<=k;i++){
for(int j=i+1;j<=n-(k-i)+1;j++){
for(int l=i;l<=n-(k-(i-1))+1&&l<j;l++){ //这里注意一个 l<j,就是你匹配的位置一定要比j这个当前位置要靠前!
dp[i][j]=max(dp[i][j],dp[i-1][l]*get(l,j-1));
}
}
}
long long ans=0;
for(int i=k+1;i<=n;i++){ //注意这里是没有算过最后一个乘号右边的值,所以比较的时候记得加上
ans=max(ans,dp[k][i]*get(i,n));
}
cout<<ans<<endl;
return 0;
}