1143 快速求和
题目描述
给定一个数字字符串,用最少次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在需要的所有加号都插入后,就象做普通加法那样来求值。 例如,考虑字符串”12”,做0次加法,我们得到数字12。如果插入1个加号,我们得到3。因此,这个例子中,最少用1次加法就得到数字3。 再举一例,考虑字符串”303”和目标数字6,最佳方法不是”3+0+3”,而是”3+03”。能这样做是因为1个数的前导0不会改变它的大小。 写一个程序来实现这个算法
分析
Dp
d[i][k]表示前i个数中凑到k的最少加号数。
g[i][j]表示第i位到第j位形成的数,可以预处理出来。
枚举前一个加号的位置j,然后转移到d[i][k+g[j+1][i]]形成递推。
即
d[i][k+g[i+1][i]]=min(d[i][k+g[i+1][i]],d[j][k]+1);
注意:
1. 由于第一个数之前是没有加号的,所以k要从0开始枚举。
2. 问题解决完了数的最后会多出一个加号来,所以ans要-1.
3. 我的代码实现中并没有直接枚举前一个加号的位置,而是枚举大小,减过去的。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 100
#define MAXM 1024
#define INF 0x3fffffff
using namespace std;
int n,len,ans=INF;
int g[MAXN+5][MAXN+5];
int d[MAXN+5][MAXM];
int main()
{
for(int i=0;i<=MAXN;i++)
for(int j=0;j<=MAXM;j++)
d[i][j]=INF;
char s[50];
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;
}
d[0][0]=-1;
for(int i=1;i<=len;i++)
for(int j=1;j<=i;j++)
{
if(g[i-j+1][i]>n)
break;
for(int k=0;k<=n;k++)
if(d[i-j][k]!=INF)
{
if(k+g[i-j+1][i]>n)
break;
d[i][k+g[i-j+1][i]]=min(d[i][k+g[i-j+1][i]],d[i-j][k]+1);
}
}
printf("%d",d[len][n]==INF?-1:d[len][n]);
}
搜索
此题亦可搜索,同样处理出数组g。同样要考虑上一个加号的位置。所以搜索时的参数可以为:
i(位置),last(上一个加号的位置),p(已用加号个数),sum(当前凑到的和)。
数据小可以水过去,但如果数据大了,有以下剪枝供参考:
1. 可行性剪枝:当前的sum已经比n要大了,退出
3. 最优性剪枝:凑到当前的sum比n要小,但是p已经大于的记录的最佳值,退出
4. 可行性剪枝:当前的凑到的sum加上之后所有数合起来的值(即之后不加任何加号所形成的数)比n要小,退出
void dfs(int i,int last,int p,int sum)
{
if(i==len)
{
sum+=g[last+1][len];
if(sum==n)
if(p<ans)
ans=p;
return ;
}
dfs(i+1,last,p,sum);
int j=g[last+1][i];
dfs(i+1,i,p+1,sum+j);
}