序列型动态规划--猜谜(三校联考试题)

【问题描述】

  给出一个长度为N的数字字符串和一个数字T,要求插入最少的加号或者乘号,使得数字字符串的运算结果为T。运算符*号优先级高于+号,运算数可以有任意个前导0.

【输入格式】

  输入不超过5组数据,每组数据两行。
  每组数据的第一行为长度N,只包含0~9的数字字符串;第二行为一个数字T。
  输入T<0表示输入结束。

【输出格式】

  输出一个数字单独占一行,表示最少需要添加的运算符(+号或*号)数,无解输出-1.

【输入样例】

032089
5
333
9
00
-1

【输出样例】

3
2

【数据范围】

对于30%的数据,有1<=N<=10,0<=T<=50.
对于50%的数据,有1<=N<=15,0<=T<=200.
对于全部的数据,有1<=N<=20,0<=T<=300.

看起来就很恶心的题目:的优先级>+,所以可以把用连接的连续数字看成一组,分组动归。
状态方程:f[i][j]表示前i个数字和为j,添加的运算符的最小个数
f[i][j]= min{ f[k][x] +1 +g[k+1][i][j-x] | 1<=k< i && j-x>=0}
边界:f[i][j]=g[1][i][j]

g[i][j][x]表示在第i~j个数字之间插入*,得到x的最少插入值<组权值>
g[i][j][x]=min{ g[i][p][k/num[p+1][j]] +1 | k%num[p+1][j]==0}
边界:g[i][j][num[i][j]]=0 |num[i][j]<=T

num[i][j]表示第i~j个数字组成一个新的数字是多少(可以有前导0)
num[i][j]=num[i][j-1]+s[j]-‘0’

实现发现问题了:
1.num数组中,若i< j,则num[i][j]的值不应为0,而是-1,如果是0的话算g的时候有问题(不过好像循环计算g[i][j][k]时保证j>=i的话也没有问题,代码中没有用num[i][j]=max(0,num[i][j-1])*10+s[j]-‘0’ );
num[i][j]可能>long long ,那也没办法,就是负数,不过应该也不存在满足的,因为T<=300,不过在判断g[i][j][num[i][j]]=0的条件应该是 if(num[i][j]<=T && num[i][j]>=0);

2.循环到多大的值一点要注意;

3.f[i][j]的边界不是简单的inf,而是g[1][i][j],如果赋成inf则答案为-1;

4.关于g[i][j][0]的问题在代码中有写,卡了挺久的;

代码如下:

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long LL;
const int maxn=25;
const int inf=100000000;
const int maxm=305;
int T,f[maxn][maxm],n,g[maxn][maxn][maxm];
char s[maxn];
int num[maxn][maxn];
void db()
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            for(int k=1;k<=T;k++) cout<<g[i][j][k]<<' ';
            cout<<endl;
        }
        cout<<endl;
    }
}
/*
    f[i][j]表示前i个数字和为j的最小插入数
    f[i][j]=min{ f[k][x]+1+g[k+1][i][j-x] | 1<=k<i && j-x>=0}
    边界:f[i][j]=g[1][i][j]

    g[i][j][k]表示用*把第i~j个数字连在一起乘积为k的最小代价 
    g[i][j][k]=min{ g[i][p][k/num[p+1][j]] +1 | k%num[p+1][j]==0}
    注意每次g[i][j][0]可以提出来特判因为不能%0
    边界:g[i][j][num[i][j]]=0  |num[i][j]<=T 
*/

void dp()
{
    for(int i=1;i<=n;i++)
    for(int j=i;j<=n;j++)
    {
        for(int k=0;k<=T;k++) g[i][j][k]=inf;
        if(num[i][j]<=T && num[i][j]>=0) g[i][j][num[i][j]]=0;//位数实在太多了爆LL没有办法,不完美 
    }

    for(int i=1;i<=n;i++)
    for(int j=i;j<=n;j++)
    {
        if(num[i][j]==0) g[i][j][0]=0;
        else 
        {
            if(num[i][i]==0 || num[j][j]==0) g[i][j][0]=min(g[i][j][0],1);
            else
            {
                for(int p=i+1;p<j;p++) g[i][j][0]=min(g[i][j][0],g[i][p][0]+1); 
            }
        }
        for(int k=1;k<=T;k++)
        {
            for(int p=i;p<j;p++)
            if(num[p+1][j]>0 && k%num[p+1][j]==0)
            {
                g[i][j][k]=min(g[i][p][k/num[p+1][j]]+1,g[i][j][k]);
            }
        }
    }


    for(int i=1;i<=n;i++)
    for(int j=0;j<=T;j++) f[i][j]=inf;

    for(int i=1;i<=n;i++)
    for(int j=0;j<=T;j++)
    {
        int t=f[i][j];
        for(int k=1;k<i;k++)
        for(int x=0;x<=j;x++) t=min(t,f[k][x]+1+g[k+1][i][j-x]);
        f[i][j]=t;
    }

//  db();
}
int main()
{
    freopen("in.txt","r",stdin);
//  freopen("out.txt","w",stdout); 
    while(scanf("%s",s+1)!=EOF)
    {
        n=strlen(s+1);
        memset(num,-1,sizeof(num));
        scanf("%d",&T);
        if(T<0) break;
        for(int i=1;i<=n;i++)
        {
            num[i][i-1]=0;
            for(int j=i;j<=n;j++) 
            {
                num[i][j]=num[i][j-1]*10+s[j]-'0';
                if(num[i][j]>T) break;
            }
        }
        dp();
        if(f[n][T]<inf) printf("%d\n",f[n][T]);
        else printf("-1\n");
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值