[NOIP2000 提高组] 乘积最大

题目背景

NOIP2000 提高组 T2

题目描述

今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰 90 周年。在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好朋友 XZ 也有幸得以参加。活动中,主持人给所有参加活动的选手出了这样一道题目:

设有一个长度为 NN 的数字串,要求选手使用 KK 个乘号将它分成 K+1K+1 个部分,找出一种分法,使得这 K+1K+1 个部分的乘积能够为最大。

同时,为了帮助选手能够正确理解题意,主持人还举了如下的一个例子:

有一个数字串:312312,当 N=3,K=1N=3,K=1 时会有以下两种分法:

  1. 3 \times 12=363×12=36
  2. 31 \times 2=6231×2=62

这时,符合题目要求的结果是:31 \times 2 = 6231×2=62。

现在,请你帮助你的好朋友 XZ 设计一个程序,求得正确的答案。

输入格式

程序的输入共有两行:

第一行共有 22 个自然数 N,KN,K。

第二行是一个长度为 NN 的数字串。

输出格式

结果显示在屏幕上,相对于输入,应输出所求得的最大乘积(一个自然数)。

输入输出样例

输入 #1复制

4 2
1231

输出 #1复制

62

说明/提示

数据范围与约定

对于 60\%60% 的测试数据满足 6≤N≤206≤N≤20。
对于所有测试数据,6≤N≤40,1≤K≤66≤N≤40,1≤K≤6。

 

我会尽可能写的容易理解owo

首先这道题很明显要dp做,因为这道题最后要求输出最大值,而该值可由两个上一级数字相乘得到。所以我们把原数先分为两块,一个由k段相乘得出的最大值和剩余部分的值,如图:

第一次分割

而几段数的最大值则可以再次分割为右侧的一段数和左侧的几段数乘积的最大值。如此不断分割,直到分割到只剩一段,此时该段的最大值就是它本身。所以只要不断分割,每次分割都取当前情况的最优解即可得到问题最优解。

接下来我们考虑怎么实现

我们用a保存原数,a[i][j]表示原数的从第i个数到第j个数大小。

举例:原数 124578

则a[2][4]保存的值则为2457。(高精度下可以用三位数组,最后一位将每一位数分别储存)

接下来我们用dp保存一段数的最大值,定义数组dp[][],dp[i][j]表示将前i个数分成j段可以得到的最大值。我们知道当该段获得最大值时,该段与该段后数字的乘积才能得到最大值。

由此我们可以得到方程:

*dp[i][j]=max(dp[i][j],dp[k-1][j-1]a[k][i])

(高精度可以通过再数组上再扩充一维来保存每一位数)

具体实现请看代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string>
using namespace std;
int dp[200][100][2000]={0},a[200][200][100]={0},n,k;
int js(int x,int y,int z);
int main(){
    //输入并处理数据
    //a[i][j]表示a的从i到j的数的大小
    //a[1][n]存储完整的数
    cin>>n>>k;
    char tin;
    a[1][n][0]=n;
    //高精度保存
    //所有数字位数保存在第[0]位
    for(int i=1;i<=n;i++){
        cin>>tin;
        a[1][n][n-i+1]=tin-'0';
    }
    //预处理
    for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j++){
            a[i][j][0]=j-i+1;
            for(int q=1;q<=a[i][j][0];q++){
                a[i][j][q]=a[1][n][q+i-1];
            }
        }
    }
    //计算
    for(int i=1;i<=n;i++){
        /*
            用dp[i][j]保存将前i个数分成j段所能得到的最大值
        */
        for(int j=1;j<=i&&j<=k+1;j++){
            /*
                计算dp[i][j]的最大值
                将前i个数字与分为最右边一段与左边最大值的乘积
                因为dp[i][j]的最大值为数字i-k段的值与dp[k][j-1]的值相乘的最大值
                k表示数字段的第一位
                dp[1][1]的
            */
            if(j==1){//只分成一段时,值为该段的数字
                dp[i][j][0]=i;
                for(int z=1;z<=i;z++){
                    dp[i][j][z]=a[1][i][z];
                }
            }
            /*
                在前i个数中寻找一个分割点
                从该点分割得到的数最大
                该点前所留的数应比j(分成的段数)大
            */
            for(int z=i;z>=j-1;z--){//遍历z,找到最优的z
                js(i,j,z);
            }
        }
    }
    for(int i=dp[n][k+1][0];i>=1;i--){
        cout<<dp[n][k+1][i];
    }
}
int js(int x,int y,int z){
    //计算乘积,保存在tsz内
    /*
        给定分割点z
        前x个数,分成y段
    */
    int tsz[200]={0};
    //高精度乘法
    for(int i=1;i<=a[z][x][0];i++){//数字a的控制位
        for(int j=1;j<=dp[z-1][y-1][0];j++){//最大值dp的控制位
            tsz[i+j-1]+=a[z][x][i]*dp[z-1][y-1][j];
        }
    }
    //计算进位与数字长度
    //所有数字长度保存在数组[0]位
    for(int i=1;i<=100;i++){
        if(tsz[i])tsz[0]=i;
        tsz[i+1]+=tsz[i]/10;
        tsz[i]%=10;
    }
    //比较乘积与原数字的大小,与dp[x][y]比较
    if(tsz[0]<dp[x][y][0]){
        return 0;
    }
    for(int i=tsz[0];i>=0;i--){
        if(i==0)return 2;//相同
        if(tsz[i]>dp[x][y][i])break;
        if(tsz[i]<dp[x][y][i])return 1;
    }
    //如果是更优解,保存该解
    for(int i=tsz[0];i>=0;i--){
        dp[x][y][i]=tsz[i];
    }
    return -1;
}

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值