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

来源:互联网 发布:热血无赖mac存档位置 编辑:程序博客网 时间:2024/06/15 02:09

【问题描述】

  给出一个长度为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;}