DP.01-最佳加法表达式

题目描述

给定n个1到9的数字,要求在数字之间摆放m个加号(加号两边必须有数字),使得所得到的加法表达式的值最小,并输出该值。例如,在1234中摆放1个加号,最好的摆法就是12+34,和为36

思路

动态规划一般是把大问题化成小问题,对小问题进行求解
有递归和递推两种思路,递归应该会更直观
递归的思路:不要考虑太多,
1.只考虑得到结果的前一步,n个数字,m个加号

total[ n ][ m ] = min ( total[ i ][ m-1 ] + number[ i +1 ][ n ] ); //i=(m,m+1…,n-1)
number[ i ][ j ] 利用数组计算的 从 i 到 j 的数值,加号最前也在 i+1 处

2.考虑边界,最低端的情况,最小问题的解

if(m==0) total[ n ][ m ]=number [ 0 ][ n ] ; //原数值
if(n < m+1) return 0; //加号多出来,无法完成表达式

3.大额数值需要高精度运算

利用数组进行进位

4.照常,我努力的把大佬的代码看懂了,大佬真的好厉害,哭泣

总体思路:第 i 行的 ret [ i-1 ][ k ] 每格都是到第 j 个字符时,有(i-1)个加号的最小值
---------------在此结果上推(i+1)行-----递推

AC代码(递推思路

#include<bits/stdc++.h>
#include<cstring>
using namespace std;
#include<stdlib.h>

const int MaxLen = 55;
const string maxv = "999999999999999999999999999999999999999999999999999999999";
string ret[MaxLen][MaxLen];
string num[MaxLen][MaxLen];

int cmp(string &num1,string &num2){//minv > tmp,更新小值
    int l1 = num1.length();
    int l2 = num2.length();
    if (l1 != l2)//直接比较长度
        return l1-l2;
    else{
        for (int i=l1-1; i>=0; i--){
            if (num1[i]!=num2[i])//比较末位/reverse的首位
                return num1[i]-num2[i];            
        }
    return 0;
     }
 }

void add (string &num1,string &num2,string &num3){
    //加法从低位到高位相加,那么需要将字符串倒过来
    int l1 = num1.length();
    int l2 = num2.length();
    int maxl = MaxLen,c = 0;//c是进位标志
     for (int i=0; i<maxl; i++){
        int t;
        
        if (i < l1 && i < l2)//相同位的加和
            t = num1[i]+num2[i]-2*'0'+c;
       
        else if (i < l1 && i >= l2)        
            t = num1[i] - '0' + c;
               
        else if (i >= l1 && i < l2)        
            t = num2[i] - '0' + c;        
       
        else //计算完成        
            break;
        
        num3.append(1,t%10+'0');
        c = t/10;
    }
       while (c)//进位
    {
        num3.append(1,c%10+'0');
        c /= 10;
    }
 }

int main(){
    int m;                  //加号数目
    string str;             //输入的字符串
    while(cin >> m >> str){
           //为了之后的加法计算先将这个字符串倒过来
        reverse(str.begin(),str.end());
        int n = str.length();

        for (int i=0; i<n; i++){        
            num[i+1][i+1] = str.substr(i,1);
        
        for (int i=1; i<=n; i++) //求解对应的num[i][j]
             for (int j=i+1; j<=n; j++)
                   num[i][j] = str.substr(i-1,j-i+1);

        //当加号数目为0
        for (int i=1; i<=n; i++)
           ret[0][i]=num[1][i];
  
        for (int i=1; i<=m; i++){ //对于加号数目的枚举
             
             for (int j=1; j<=n; j++){ //加号可以安排的范围             
                 string minv = maxv;
                 string tmp;
                      //针对每次加号的位置重置变量
                 for (int k=i; k<=j-1; k++){//枚举加号的摆放位置
                      tmp.clear();
                      add(ret[i-1][k],num[k+1][j],tmp);

                      if (cmp(minv,tmp)>0)
                          minv = tmp;
                     }
                 ret[i][j] = tmp;
               }
          }
          
          reverse(ret[m][n].begin(),ret[m][n].end());//把颠倒的顺序
          cout << ret[m][n] << endl;
         
    }
      return 0}

心得

1.num[ k+1 ][ j ]: 记录从第 i 个数字开始 到第 j 的字符串

--------------------把所有加号摆放结果的字符串都储存进去,必要时取出
在这里插入图片描述

2.ret[ i-1 ][ k ]: 储存前 k 个字符的字符串
------------------- i 表示有i个加号
-------------------minv/tmp : 表示minv开始和tmp比较在这里插入图片描述
3.add函数: 针对每次加号的摆放位置( k ),把加号前的字符串 ret 和 num 加在一起
和上一次的结果进行比较(枚举在字符 j 前加号的摆放位置),选出字符串到 j 的最小值,再向后延申一位
在这里插入图片描述

新学到的东西

1)string . append() 添加文本

  1. 在str后面添加一个完整字符串
    str . append( c );

  2. 在string后面添加一部分字符串

    str . append( c , n )把 c 前的 n 个字符加到 str 后面
    str . append( c , n , m )把 c 的从 n 开始的 m 个字符加到 str 后面

  3. 在string后面加多个相同字符

    str . append( m , ‘x’ )在str后面增加 m 个相同字符

2)string . substr() 截取一部分字符

1.string . substr(startnum , len )

startnum:起始字符的序号 len:从起始字符截取的字符串长度

递推思路

在这里插入图片描述递归感觉要比递推好理解,但是用递归写可能会超时

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值