缩水版本
题目描述
时间限制: 10 Sec 内存限制: 256 MB
给定一个数字字符串,用最少次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在需要的所有加号都插入后,就象做普通加法那样来求值。 例如,考虑字符串”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
分析 代码
刚看这道题还以为多难,结果去看了看时间限制(10 Sec),呵呵。
对于此题,我就啥也没优化的去弄了个dfs。
就是搜索加号的位置,再转化为整型加上去。
一代无优化纯爆搜代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
char c[25],h[25];int t,n,p,k[25],w;
void dfs(int x)
{
if(x==w-1)
{
int e=0,u=0,r;
for(int i=0;i<w-1;i++)
if(k[i])
{
r=(c[i]-'0');p=1;
for(int j=i-1;j>=0;j--)
if(!k[j]){r=r+(c[j]-'0')*pow(10,p);p++;}
else break;
e+=r;u++;
}
r=0,p=0;
for(int i=w-1;i>=0;i--)
if(k[i])break;
else{r=r+(c[i]-'0')*pow(10,p);p++;}
e+=r;
if(e==n&&u<t)
t=u;
return ;
}
k[x]=1;
dfs(x+1);
k[x]=0;
dfs(x+1);
}
int main()
{
scanf("%s %s",c,h);
int q=strlen(h);
t=w=strlen(c);
for(int i=0;i<q;i++)
n=n*10+int(h[i]-'0');
dfs(0);
if(t<w)printf("%d\n",t);
else printf("-1\n");
}
但总觉得对不起这么大的空间限制(256 MB)。
于是乎,我们就可以初始化一个数组,装所有状态下每几个数间所有数的和。(也就是下列代码的g数组)
同时我们也可以对函数进行一个优化。(多几个变量,海阔天空)
而且我们找的也只是最少的加号数。
所以我们就可以进行剪枝,如果当前加号比目前答案大,就直接退出了。
同理,如果当前总和比N大,也就直接退了。
二代改进代码
#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)
{
/*
x:查找到第几个数
last:上一个加号位置
p:目前有几个加号
sum:目前总和
*/
if(sum+g[last+1][x]>n)return;
if(p>=ans)return;
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 Sec 内存限制: 128 MB
给定一个数字字符串,用最少次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在需要的所有加号都插入后,就象做普通加法那样来求值。 例如,考虑字符串”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
分析 代码
(感觉整个人都不好了)
现在就肯定不能用普通的DFS了。
于是乎,记忆化DFS又来了。
这就像是在一个范围内查找,再记忆化。
终极代码
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=100005;
int n,len,memo[45][100005],g[45][45];/*memo[i][j]:前i个数组成j的最小加号个数
*/char c[45];
int DP(int i,int goal)
{
int t,nxt,minp=MAXN;
if(i>len)
{
if(!goal)return 0;
return MAXN;
}
if(memo[i][goal]!=0)
return memo[i][goal];
int &ans=memo[i][goal];
for(int j=i;j<=len;j++)
{
t=g[i][j];
if(t>goal)break;
nxt=DP(j+1,goal-t);
if(nxt<minp)minp=nxt;
}
if(minp<MAXN)ans=minp+1;
else ans=MAXN;
return ans;
}
void read()
{
scanf("%s%d",c,&n);
len=strlen(c);
for(int i=1;i<=len;i++)
g[i][i]=c[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]>MAXN)g[i][j]=MAXN;
}
int u=DP(1,n);
if(u==MAXN)printf("-1\n");
else printf("%d\n",u-1);
}
int main()
{read();}
最后再提一下,DP也可以做这道题,具体思路同上,这里就不多说了。