前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >最佳加法表达式 DP

最佳加法表达式 DP

作者头像
ACM算法日常
发布2019-06-11 15:51:35
6380
发布2019-06-11 15:51:35
举报
文章被收录于专栏:ACM算法日常ACM算法日常

题意:

有一个由1..9组成的数字串.问如果将m个加号插入到这个数字串中,在各种可能形成的表达式中,值最小的那个表达式的值是多少。 输入: 5 3 1 2 3 4 5 输出: 24

核心内容总结:所谓的DP是指 从最小的子结构最优,推往全局的过程。

跟是不是递推或者dfs还是记忆化无半毛钱关系。

重剑无锋大巧不工,贵在领悟精神。

今天写这篇主要是为了总体的认识一下DP。

DP是什么呢:说白了就是一个找最优子结构的过程,至于愿怎么找,那就随意了。

但说白了核心还是通过遍历(暴力,怎么暴力怎么来)来找各个最优子结构,由最小的局部最优推至全局。

其实拿for循环,循环套循环和dfs都一样,其实都是从最局部最小的问题最优,推至全局最优。

综上总结:1.递推(自底向上法) 2.递归(带备忘的自顶向下法) 其实都是一种东东。

这道题比较典型,就拿这道题为例吧!

在10个数字中放任意个加号使得组成的表达式的和最小。

状态转移方程:

将m个加号插入到n个数字组成的数字串中

V(m,n) 表示将m个加号插入到n个数字组成的数字串中组成的表达式和最小的表达式

i表示在第i个数后面插入加号

V(m,n) = Min{ V(m-1,i) + Num(i+1,n) } ( i = m … n-1)

表达式的最后一个加号添加在第 i 个数字后面

这个i要试遍所有的情况

1.先写一下裸跑dfs,由于不用记忆化,新手的话比较容易理解。

坏处是不开记忆化单纯费时间,因为暴搜的过程中产生了许多冗余的搜索。

好处是不开的话,省下了记忆各个最优子结构的内存。

具体取舍,开还是不开根据实际情况来定。

代码语言:javascript
复制
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int INF = 99999999;
int a[1005],num[1005][1005];
int V(int m,int n)
{
    if(m == 0)//无加号
        return num[1][n];
    else if(n < m+1)//加号过多
        return INF;
    else
    {
        int t = INF;
        //这里有点像回溯,回溯的话递归里面有循环
        //说说实话,递归真的很简单,就是把递推公式写进来
        for(int i = m;i <= n-1;i++) //这里是n减一,排除了最后一个数字后面放加号的情况
           t = min(t, V(m-1,i)+num[i+1][n]);
        //不懂的位置画个实例,会很简单,因为熟悉,所以容易
        return t;
    }
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i = 1;i <= n;i++)
            scanf("%d",&a[i]);
        //预处理,计算i到j数字串组成的数字
        for(int i = 1;i <= n;i++)
        {
            num[i][i] = a[i];//只有一个数字
            for(int j = i+1;j <= n;j++)
            {
                //这个表达式也可以好好看一下
                num[i][j] = num[i][j-1]*10 + a[j];
            }
        }
        cout<< V(m,n) <<endl;
    }
    return 0;
}

2.记忆化搜索

代码语言:javascript
复制
int V(int m,int n) {
   if(v[m][n]!=0)return v[m][n];
   for(int i = m;i <= n-1;i++)
      t = min(t, V(m-1,i)+num[i+1][n]);
   return v[m][n]=t;
}
既然已经知道当前子结构的最优情况,爆破再遇见时就不用再扫一遍了。
所以因为少扫了很多东西,所以很省时间。
但也得开内存去存各个子结构的最优情况!!!
代码语言:javascript
复制
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int INF = 99999999;
int a[1005],num[1005][1005];
int v[1005][1005];
int V(int m,int n)
{
    int t = INF;
    if(v[m][n]!=0)return v[m][n];
    if(m == 0)//无加号
        return num[1][n];
    else if(n < m+1)//加号过多
        return INF;
    else
    {
        //这里有点像回溯,回溯的话递归里面有循环
        //说说实话,递归真的很简单,就是把递推公式写进来
        for(int i = m;i <= n-1;i++) //这里是n减一,排除了最后一个数字后面放加号的情况
           t = min(t, V(m-1,i)+num[i+1][n]);
        //不懂的位置画个实例,会很简单,因为熟悉,所以容易
    }
    return v[m][n]=t;
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i = 1;i <= n;i++)
            scanf("%d",&a[i]);
        //预处理,计算i到j数字串组成的数字
        for(int i = 1;i <= n;i++)
        {
            num[i][i] = a[i];//只有一个数字
            for(int j = i+1;j <= n;j++)
            {
                //这个表达式也可以好好看一下
                num[i][j] = num[i][j-1]*10 + a[j];
            }
        }
        cout<< V(m,n) <<endl;
    }
    return 0;
}

3.自下而上的递推 也就是一般而言的DP

代码语言:javascript
复制
留意一下,确实是 i=1,j=i,k=i开始扫的
利用for循环遍历所有情况,而且是自底向上,通过最小单位的子结构 来推往全局的。
for(int i=1;i<=m;i++)
    for(int j=i;j<=n;j++)
        for(int k=i;k<=j;k++)
代码语言:javascript
复制
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=1005;
int a[N],num[N][N],dp[N][N];
//a[N]里面是存数字串
//num[i][j]表示数字串a[N]的第i位到第j位之间的数字串表示的数组
//dp[i][j]在i个数字中插入j个加号所能形成的表达式最小值
int main(){
    int n,m;
    while(scanf("%d %d",&n,&m)){
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        //预处理,计算i到j数字串组成的数字
        for(int i=1;i<=n;i++){
            num[i][i]=a[i];//只有一个数字
            for(int j=i+1;j<=n;j++){
                num[i][j]=num[i][j-1]*10+a[j];
            }
        }
        memset(dp,0x3f,sizeof(dp));
        for(int i=1;i<=n;i++){
            dp[0][i]=num[1][i];//无加号时
        }
        //其实就是感觉在那个位置放不放加号
        //这里n可以写在m前面。要加一个限制条件n>m,好麻烦,所以m在前且n=m+1
        //这里k的取值范围就是m到n,k表示在第k个数后面插入加号
        for(int i=1;i<=m;i++)
            for(int j=i;j<=n;j++)
                for(int k=i;k<=j;k++)
                    dp[i][j]=min(dp[i][j],dp[i-1][k]+num[k+1][j]);
        cout<<dp[m][n]<<endl;
    }
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ACM算法日常 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档