快速求和(炒鸡数据版)

缩水版本

题目描述

时间限制: 10 Sec 内存限制: 256 MB
给定一个数字字符串,用最少次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在需要的所有加号都插入后,就象做普通加法那样来求值。 例如,考虑字符串”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

分析 代码

刚看这道题还以为多难,结果去看了看时间限制(10 Sec),呵呵。
对于此题,我就啥也没优化的去弄了个dfs。
就是搜索加号的位置,再转化为整型加上去。

一代无优化纯爆搜代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
char c[25],h[25];int t,n,p,k[25],w;
void dfs(int x)
{
    if(x==w-1)
    {
        int e=0,u=0,r;
        for(int i=0;i<w-1;i++)
            if(k[i])
            {   
                r=(c[i]-'0');p=1;
                for(int j=i-1;j>=0;j--)
                    if(!k[j]){r=r+(c[j]-'0')*pow(10,p);p++;}
                    else break;
                e+=r;u++;
            }
        r=0,p=0;
        for(int i=w-1;i>=0;i--)
            if(k[i])break;
            else{r=r+(c[i]-'0')*pow(10,p);p++;}
        e+=r;
        if(e==n&&u<t)
            t=u;
        return ;
    }
    k[x]=1;
    dfs(x+1);
    k[x]=0;
    dfs(x+1);
}
int main()
{
    scanf("%s %s",c,h);
    int q=strlen(h);
    t=w=strlen(c);
    for(int i=0;i<q;i++)
    n=n*10+int(h[i]-'0');
    dfs(0);
    if(t<w)printf("%d\n",t);
    else printf("-1\n");
}

但总觉得对不起这么大的空间限制(256 MB)。
于是乎,我们就可以初始化一个数组,装所有状态下每几个数间所有数的和。(也就是下列代码的g数组)
同时我们也可以对函数进行一个优化。(多几个变量,海阔天空)
而且我们找的也只是最少的加号数。
所以我们就可以进行剪枝,如果当前加号比目前答案大,就直接退出了。
同理,如果当前总和比N大,也就直接退了。

二代改进代码

#include<cstdio>
#include<cstring>
const int INF=100000000;
char s[25];
int n,len,g[25][25],ans=INF;
void read()
{
    scanf("%s%d",s,&n);
    len=strlen(s);
    for(int i=1;i<=len;i++)
        g[i][i]=s[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]>INF)g[i][j]=INF;
        }
}
void dfs(int x,int last,int p,int sum)
{
/*
    x:查找到第几个数 
    last:上一个加号位置 
    p:目前有几个加号 
    sum:目前总和
*/
    if(sum+g[last+1][x]>n)return;
    if(p>=ans)return;
    if(x==len)
    {
        sum+=g[last+1][x];
        if(sum==n&&p<ans)ans=p;
        return;
    }
    dfs(x+1,last,p,sum);
    int t=g[last+1][x];
    dfs(x+1,x,p+1,sum+t);   
}
int main()
{
    read();
    dfs(1,0,0,0);
    if(ans!=INF)printf("%d\n",ans);
    else printf("-1\n");
    return 0;
}

炒鸡版本

题目描述

时间限制: 1 Sec 内存限制: 128 MB

给定一个数字字符串,用最少次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在需要的所有加号都插入后,就象做普通加法那样来求值。 例如,考虑字符串”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

分析 代码

(感觉整个人都不好了)
现在就肯定不能用普通的DFS了。
于是乎,记忆化DFS又来了。
这就像是在一个范围内查找,再记忆化。

终极代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=100005;
int n,len,memo[45][100005],g[45][45];/*memo[i][j]:前i个数组成j的最小加号个数
*/char c[45];
int DP(int i,int goal)
{
    int t,nxt,minp=MAXN;
    if(i>len)
    {
        if(!goal)return 0;
        return MAXN;
    }
    if(memo[i][goal]!=0)
        return memo[i][goal];
    int &ans=memo[i][goal];
    for(int j=i;j<=len;j++)
    {
        t=g[i][j];
        if(t>goal)break;
        nxt=DP(j+1,goal-t);
        if(nxt<minp)minp=nxt;
    }
    if(minp<MAXN)ans=minp+1;
    else ans=MAXN;
    return ans;
}
void read()
{
    scanf("%s%d",c,&n);
    len=strlen(c);
    for(int i=1;i<=len;i++)
        g[i][i]=c[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]>MAXN)g[i][j]=MAXN;
        }
    int u=DP(1,n);
    if(u==MAXN)printf("-1\n");
    else printf("%d\n",u-1);
}
int main()
{read();}

最后再提一下,DP也可以做这道题,具体思路同上,这里就不多说了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值