快速求和
给定一个数字字符串,用最少次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在需要的所有加号都插入后,就象做普通加法那样来求值。 例如,考虑字符串"12",做0次加法,我们得到数字12。如果插入1个加号,我们得到3。因此,这个例子中,最少用1次加法就得到数字3。 再举一例,考虑字符串"303"和目标数字6,最佳方法不是"3+0+3",而是"3+03"。能这样做是因为1个数的前导0不会改变它的大小。 写一个程序来实现这个算法。
Input
第1行:1个字符串S(1<=length(S)<=20)和1个整数N(N<=2^9-1)。S和N用空格分隔。
Output
第1行:1个整数K,表示最少的加法次数让S等于N。如果怎么做都不能让S等于N,则输出-1。
Sample Input
2222 8
Sample Output
3
Analysis
首先,考虑深度优先搜索,枚举每一个位置,可以选择在那里放加号或不放。
放加号时,就相当于把这个数分为前后两个部分,所以我们考虑预处理,将每一段用数组存起来。
for(int i=1;i<=len;i++)//len为输入字符串长度
g[i][i]=s[i-1]-'0';//从第i位到第i位就是这一位本身
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];//从i到j组成的就是从i到j-1的数*10+j这一位
if(g[i][j]>INF)g[i][j]=INF;//INF为定义的极大值
//数据有可能超出范围,当超出极大值时,就肯定不能加上一个数组成n,就赋成极大值
}
所以就开始写深度优先搜索的函数
void dfs(int x,int last,int p,int sum)
//x为当前位数,laxt为上一次放加号的位置的前一位,p为已放加号的个数,sum为目前的和
{
if(x==len)//当找到最后一位时
{
sum+=g[last+1][x];//由于这一位还没加,所以加上
if(sum==n&&p<ans)ans=p;//刷新ans的值
return;//返回(很重要)
}
dfs(x+1,last,p,sum);//不加加号
int t=g[last+1][x];
dfs(x+1,x,p+1,sum+t);//加上加号,把上一个阶段的数加到总和中
}
代码如下
#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)
{
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.当当前阶段sum的值累加已经超过n时就可以直接返回上一阶段,因为这是做加法。
2.当目前使用加号已超过ans的值时,也返回。
#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)
{
if(sum+g[last+1][x]>n)return;//大于n
if(p>=ans)return;//大于ans
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;
}
接下来,看一道加强版。(题目描述一样,数据不同)
Input
第1行:1个字符串S(1<=length(S)<=40)和1个整数N(N<=10^5)。S和N用空格分隔。
Output
第1行:1个整数K,表示最少的加法次数让S等于N。如果怎么做都不能让S等于N,则输出-1。
Sample Input
2222222222222222222222222222222222222222 80
Sample Output
39
/(ㄒoㄒ)/~~
X﹏X
看这个样子,用DFS不行,用BFS也不行,但我们可以用记忆化深搜。
#include<cstdio>
#include<cstring>
const int MAXN=100,MAXV=100005;
char s[MAXN];
int n,len,memo[MAXN][MAXV],g[MAXN][MAXN];
//memo[i][j]表示从i到len组成j用的最少加号数
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]>MAXV)g[i][j]=MAXV;
}
}
int dfs(int x,int goal)//表示从x到len组成goal需要的最少加号数
{
int t,nxt,minp=MAXN;
if(x>len)//边界
{
if(!goal)return 0;
else return MAXN;
}
if(memo[x][goal])return memo[x][goal];//已经计算过
int &ans=memo[x][goal];//ans为memo的地址
for(int i=x;i<=len;i++)//开始枚举
{
t=g[x][i];
if(t>goal)break;//当这个数大于要凑的数时,就直接退出循环
nxt=dfs(i+1,goal-t);
if(nxt<minp)minp=nxt;
}
if(minp<MAXN)ans=minp+1;
else ans=MAXN;//更新ans的值(实际更新了memo[x][goal]的值)
return ans;
}
int main()
{
read();
int an=dfs(1,n);
if(an!=MAXN)printf("%d\n",an-1);//因为默认了最开头有一个加号
else printf("-1\n");
}