快速求和
版本一:
题目描述
给定一个数字字符串,用最少次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在需要的所有加号都插入后,就象做普通加法那样来求值。 例如,考虑字符串”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
不用去重新看题了,我告诉你这道题就是比上一题数据大一点
所以深搜就必定会超时
法一:
记忆化搜索
定义状态: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]);
}