DP——快速求和

快速求和
版本一:
题目描述
给定一个数字字符串,用最少次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在需要的所有加号都插入后,就象做普通加法那样来求值。 例如,考虑字符串”12”,做0次加法,我们得到数字12。如果插入1个加号,我们得到3。因此,这个例子中,最少用1次加法就得到数字3。 再举一例,考虑字符串”303”和目标数字6,最佳方法不是”3+0+3”,而是”3+03”。能这样做是因为1个数的前导0不会改变它的大小。 写一个程序来实现这个算法。

输入
第1行:1个字符串S(1<=length(S)<=20)和1个整数N(N<=2^9-1)。S和N用空格分隔。

输出
第1行:1个整数K,表示最少的加法次数让S等于N。如果怎么做都不能让S等于N,则输出-1。

样例输入

2222 8
样例输出
3

这个基础题数据较小,于是可以用深搜骗过:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char a[50];
bool f[50],f1;
int n,len,ans=50,m;
long long g[50][50];
inline void dfs(int x)
{
    if(x<=0)//边界
    {
        f[len]=1;//在最后模拟一个加号
        int x1=0;
        long long ml=0;
        for(int i=0;i<len;i++)
            if(f[i+1])
                ml+=g[x1][i],x1=i+1;//用已得到的加号将原数分割
        if(ml==n)//因为是从小到大枚举,得到答案后可以直接退出
        {
            ans=min(m,ans);
            f1=1;
        }
        f[len]=0;
        return;
    }
    for(int i=1;i<len;i++)
        if(!f[i])
        {
            f[i]=1;
            dfs(x-1);
            if(f1) return;
            f[i]=0;
        }
}
int main()
{
    scanf("%s",a);                                  
    len=strlen(a);
    scanf("%d",&n);
    for(int i=0;i<len;i++)
        g[i][i]=a[i]-'0';
    for(int i=0;i<len-1;i++)
        for(int j=i+1;j<len;j++)
            g[i][j]=g[i][j-1]*10+g[j][j];//原数第i至j位组成的数
    for(int i=0;i<len;i++)//枚举加号的个数
    {
        m=i;
        dfs(i);
        memset(f,0,sizeof(f));
        if(f1)
        {
            printf("%d",ans);
            return 0;
        }
    }   
    printf("-1");
}

看看标题,很明显这不是我要讲的重点,于是我们来看看这道题:
版本二:
题目描述
给定一个数字字符串,用最少次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在需要的所有加号都插入后,就象做普通加法那样来求值。 例如,考虑字符串”12”,做0次加法,我们得到数字12。如果插入1个加号,我们得到3。因此,这个例子中,最少用1次加法就得到数字3。 再举一例,考虑字符串”303”和目标数字6,最佳方法不是”3+0+3”,而是”3+03”。能这样做是因为1个数的前导0不会改变它的大小。 写一个程序来实现这个算法。

输入
第1行:1个字符串S(1<=length(S)<=40)和1个整数N(N<=10^5)。S和N用空格分隔。

输出
第1行:1个整数K,表示最少的加法次数让S等于N。如果怎么做都不能让S等于N,则输出-1。

样例输入

2222222222222222222222222222222222222222 80
样例输出
39

扫描二维码关注公众号,回复: 2600027 查看本文章

不用去重新看题了,我告诉你这道题就是比上一题数据大一点
所以深搜就必定会超时

法一:
记忆化搜索
定义状态:mzls(x, ml) { 在g[x][len]中凑成ml需要最少添加的加号个数}
状态转移:枚举第一个加号的位置j,设t=g[i][j],那么子问题将是:mzls(j+1, ml - t);
另外用memo来储存答案,保证不会重复搜索。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100000000//借一个极大值来储存错误
char a[50];
int n,len,m,memo[1000][1000];
long long g[50][50];
int mzls(int x,int ml)
{
    int t,x1,minp=N;
    if(x>len)//边界
    {
        if(ml==0)
            return 0;
        else
            return N;
    }
    if(memo[x][ml]!=-1)//已经到过一次就不用再进入了
        return memo[x][ml];
    for(int j=x;j<=len;j++)
    {
        t=g[x][j];
        if(t>ml) break;
        x1=mzls(j+1,ml-t);
        minp=min(minp,x1);//储存答案
    }
    memo[x][ml]=minp+1;
    return memo[x][ml];
}
int main()
{
    scanf("%s",a);                                                          
    len=strlen(a);
    scanf("%d",&n);
    memset(memo,-1,sizeof(memo));//都没有到达
    for(int i=1;i<=len;i++)
        g[i][i]=a[i-1]-'0';
    for(int i=1;i<len;i++)
        for(int j=i+1;j<=len;j++)
        {
            g[i][j]=g[i][j-1]*10+g[j][j];
            if(g[i][j]>N)
                g[i][j]=N;
        }
    int ans=mzls(1,n);
    if(ans<N)
        printf("%d",ans-1);
    else
        printf("-1");
}

法二:
DP
本质上就是记忆化搜索的递推形式
定义状态:
f[i][j]:前i个数中凑到整数k的最少加号数
g[i][j]:第i位到第j位形成的数
状态转移方程式:f[i][k+g[i-j+1][i]]=min(f[i][k+g[i-j+1][i]],f[i-j][k]+1);

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100000000
char a[50];
int n,len,f[45][100001];//f[i][j]表示前i个数中凑到整数k的最少加号数
long long g[50][50];
int main()
{
    scanf("%s",a);                                                          
    len=strlen(a);
    scanf("%d",&n);
    for(int i=0;i<45;i++)
        for(int j=0;j<100001;j++)
            f[i][j]=N;
    for(int i=1;i<=len;i++)
        g[i][i]=a[i-1]-'0';
    for(int i=1;i<len;i++)
        for(int j=i+1;j<=len;j++)
        {
            g[i][j]=g[i][j-1]*10+g[j][j];
            if(g[i][j]>N)
                g[i][j]=N;
        }
    f[0][0]=-1;//0个数什么都无法凑成
    for(int i=1;i<=len;i++)//枚举终点
        for(int j=1;j<=i&&g[i-j+1][i]<=n;j++)//枚举最后一个加号的位置,如果超过n,则提前退出
            for(int k=0;k<=n;k++)//枚举要凑成的数k
                if(f[i-j][k]!=N&&k+g[i-j+1][i]<=n)  
                    f[i][k+g[i-j+1][i]]=min(f[i][k+g[i-j+1][i]],f[i-j][k]+1);
    printf("%d",f[len][n]==N?-1:f[len][n]);
}

猜你喜欢

转载自blog.csdn.net/qq_37657307/article/details/78784551