快速求和
給定一個數字字符串,用最少次數的加法讓字符串等於一個給定的目標數字。每次加法就是在字符串的某個位置插入一個加號。在需要的所有加號都插入後,就象做普通加法那樣來求值。 例如,考慮字符串"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");
}