蓝桥杯 算法训练 乘积最大 By Assassin [dp水题]

问题描述 

  今年是国际数学联盟确定的“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[i1][k])i<=k<=n(k(i1))+1

注意了!可以看到上面k的范围很是诡异,这也是我们需要注意的地方,假定我们按照上面的规则向字符串中放置乘号,假设总共要放k个乘号,现在要放置第i个乘号,那么是不是这个称号之前还要放m-1个乘号,在它之后还要放k-i个乘号?为了保证乘号是有意义的,那么两个乘号之间至少存在一位,又根据上面右闭又开的规则,我们知道起点是(m-1)+1 .注意插一句!现在说为什么要选择左闭右开,这样就能保证在最后一个乘号插入后右边一定有数字保证合法性。 现在继续说终点,右端点的最大位置选择是n-(k-m)+1,这个+1就是看的左闭右开的规则的。 请好好自己推算一下!否则很容易乱掉

下main分享我的代码,注意我的代码中dp的起始是从1开始的,而字符串启示是从0开始的,大概的讲解我已经写在注释中了

#include
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//这里注意一个 l
                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<return 0;
}

你可能感兴趣的:(DP,蓝桥杯)