快速求和
来源:互联网 发布:淘宝店铺模板修改 编辑:程序博客网 时间:2024/05/22 01:28
题目
1.1题目描述
给定一个数字字符串,用最少次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在需要的所有加号都插入后,就象做普通加法那样来求值。 例如,考虑字符串”12”,做0次加法,我们得到数字12。如果插入1个加号,我们得到3。因此,这个例子中,最少用1次加法就得到数字3。 再举一例,考虑字符串”303”和目标数字6,最佳方法不是”3+0+3”,而是”3+03”。能这样做是因为1个数的前导0不会改变它的大小。 写一个程序来实现这个算法。
1.2输入
第1行:1个字符串S和1个整数N。S和N用空格分隔。
1.3输出
第1行:1个整数K,表示最少的加法次数让S等于N。如果怎么做都不能让S等于N,则输出-1。
1.4样例输入输出
样例输入#1
2222 8
样例输出#1
3
样例输入#2
2222222222222222222222222222222222222222 80
样例输出#2
39
数据范围
本题分为两个版本
版本1 1<=length(S)<=10,N<=10^9-1
版本2 1<=length(S)<=40,N<=10^5
题解
2.1 开局直接搜(bao)索(li)
先来看看版本1吧…Emmmm…数据水到和谐啊2333….
很明显的深搜然而本蒟蒻还是看错了题
直接仿造子集问题,生成全部可能的子集再判断即可
代码如下
#include<iostream>#include<cstdio>#include<algorithm>#include<climits>#include<cstring>using namespace std;char s[25];int f[25][25];int re,ans=INT_MAX;void dfs(int deep,int last,int num,int sum){ if(deep==strlen(s+1)) { sum+=f[last+1][strlen(s+1)]; if(sum==re)ans=min(ans,num); return ; } dfs(deep+1,last,num,sum);//不选s[deep] dfs(deep+1,deep,num+1,sum+f[last+1][deep]);//选s[deep]}int main(){ scanf("%s",s+1);scanf("%d",&re); for(int i=1;i<=strlen(s+1);i++)f[i][i]=s[i]-'0'; for(int i=1;i<=strlen(s+1);i++) for(int j=i+1;j<=strlen(s+1);j++) {f[i][j]=f[i][j-1]*10+f[j][j]; if(j-i>9)f[i][j]=INT_MAX;} dfs(1,0,0,0); if(ans==INT_MAX)printf("-1"); else printf("%d",ans);}
2.2继续剪枝优化
就这样,水到不正常的版本1就AC了…然而还有版本2呢233…这时我们就应该想到剪枝了~
我们知道剪枝分为可行性剪枝和最优性剪枝。
恰巧的是,这道题两个都可以使用。
可行性剪枝:本问题的主参数i,如果从last+1到i的距离太大,生成的整数加上之前的部分和sum,已经超过n,则退出枚举。因为继续往后枚举,生成的数会更大,更不会符合要求。
最优性剪枝:假设之前已经得到的可行解记录在ans中,如果在当前阶段放的加号个数p已经大于等于ans,那么这次搜索再继续下去是不可能找到更加优秀的解,因此必须提前退出。
加了这两个剪枝后,代码效率明显变高了。
代码如下
#include<iostream>#include<cstdio>#include<algorithm>#include<climits>#include<cstring>using namespace std;char s[45];int f[45][45];int re,ans=INT_MAX;void dfs(int deep,int last,int num,int sum){ if(sum+f[last+1][deep]>re) return;//可行性剪枝 if(num>=ans) return; //最优性剪枝 if(deep==strlen(s+1)) { sum+=f[last+1][strlen(s+1)]; if(sum==re)ans=min(ans,num); return ; } dfs(deep+1,last,num,sum);//不选s[deep] dfs(deep+1,deep,num+1,sum+f[last+1][deep]);//选s[deep]}int main(){ scanf("%s",s+1);scanf("%d",&re); for(int i=1;i<=strlen(s+1);i++)f[i][i]=s[i]-'0'; for(int i=1;i<=strlen(s+1);i++) for(int j=i+1;j<=strlen(s+1);j++) {f[i][j]=f[i][j-1]*10+f[j][j]; if(j-i>9)f[i][j]=INT_MAX;} dfs(1,0,0,0); if(ans==INT_MAX)printf("-1"); else printf("%d",ans);}
这样做可以过版本2的大部分数据。
什么???只能过大部分数据??
试试这个数据你就知道了
样例输入#3
2222222222222222222222222222222222222222 9999
样例输出#3
-1
是不是有一种慢到绝望的感觉?
分析算法可以发现,当剪枝遇到无解的情况时,剪枝搜索又退化成了普通的暴力搜索了。 虽然可能可以用clock()卡过这题然而并不支持这种做法...
现在,能走的路也只有记忆化搜索了…
2.3记忆化搜索
其实记忆化也就是优化最优性剪枝啦~
通过观察我们可以发现我们不仅仅可以把大于ans的方案剪掉,我们甚至可以定义一个f[i][j]表示走到i格时离目标goal的距离j时所花费的最小代价。也就是走到i,j时如果花费大于f[i][j]时,可将其直接剪掉。
换种说法,也等价于到达i,j时直接搜索后方的数,在满足条件的方案中选出最小值即可。
另外2.2的可行性剪枝也可以继续使用哦~
代码实现
#include<iostream>#include<cstdio>#include<algorithm>#include<climits>#include<cstring>using namespace std;char s[45];int sum[45][45];int f[45][100005];int re;int dp(int deep,int goal){ int minn_ans=INT_MAX,ksum; if(deep>strlen(s+1)) {if(goal==0)return 0;else return INT_MAX;} if(f[deep][goal]!=-1)return f[deep][goal]; for(int j=deep;j<=strlen(s+1);j++) { int ksum=sum[deep][j]; if(ksum>goal)break;//剪枝 int kkk=dp(j+1,goal-ksum); minn_ans=min(minn_ans,kkk); } if(minn_ans<INT_MAX)f[deep][goal]=minn_ans+1; else f[deep][goal]=INT_MAX; return f[deep][goal];}int main(){ scanf("%s",s+1);scanf("%d",&re); for(int i=1;i<=strlen(s+1);i++) for(int j=0;j<=re;j++)f[i][j]=-1; for(int i=1;i<=strlen(s+1);i++)sum[i][i]=s[i]-'0'; for(int i=1;i<=strlen(s+1);i++) for(int j=i+1;j<=strlen(s+1);j++) {sum[i][j]=sum[i][j-1]*10+sum[j][j]; if(j-i>9)sum[i][j]=INT_MAX;} int ans=dp(1,re); if(ans==INT_MAX)printf("-1"); else printf("%d",ans-1);}
这样,这道题目就解决了。
然而已经有了记忆化搜索,DP行不行呢?
4.4 DP出现
显然,DP当然行啦~
这次我们反着定义状态
定义f[i][k]表示前i个数中凑到整数k的最少加号数。
g[i][j]表示第i位到第j位形成的数,可以预处理出来。与之前的预处理相同。
枚举前一个加号的位置j,然后从f[i][k-g[j+1][i]]转移过来,形成递推。即
f[i][k]=min(f[i][k], f[i][k-g[j+1][i]]+1)
或者把状态转移方程写成:
f[i][k+g[j+1][i]]=min(f[i][k+g[j+1][i]],f[j][k]+1)
这样,DP就可以轻松地解决这道题啦~
代码如下
#include<iostream>#include<cstdio>#include<algorithm>#include<climits>#include<cstring>using namespace std;char s[45];int sum[45][45];int f[45][100005];int re;int main(){ scanf("%s",s+1);scanf("%d",&re); for(int i=0;i<=strlen(s+1);i++)//注意是从0开始 for(int j=0;j<=re;j++)f[i][j]=INT_MAX; for(int i=1;i<=strlen(s+1);i++)sum[i][i]=s[i]-'0'; for(int i=1;i<=strlen(s+1);i++) for(int j=i+1;j<=strlen(s+1);j++) {sum[i][j]=sum[i][j-1]*10+sum[j][j]; if(j-i>9)sum[i][j]=INT_MAX;} f[0][0]=-1; for(int i=1;i<=strlen(s+1);i++) for(int j=1;j<=i;j++) { if(sum[i-j+1][i]>re)break; for(int k=0;k<=re;k++) if(f[i-j][k]!=INT_MAX) { if(k+sum[i-j+1][i]>re)break;//仍然是剪枝 f[i][k+sum[i-j+1][i]]=min(f[i][k+sum[i-j+1][i]],f[i-j][k]+1); } } if(f[strlen(s+1)][re]==INT_MAX)printf("-1"); else printf("%d",f[strlen(s+1)][re]);}
就这样,这道题就成功解决了。
当然你也可以试试怎样用clock卡过这道题了233…
- 快速求和
- 快速求和
- 等比数列求和快速幂
- 1143 快速求和
- BSOJ3299 洛谷P1874 快速求和
- DP——快速求和
- poj 3233 矩阵快速幂 + 二分求和
- NYOJ420 p次方求和 快速幂取模
- 【p次方求和(快速幂模版)】
- nyoj 420 p次方求和 【快速幂】
- ACdream1007 a+b 快速幂求和
- nyoj420 P次方求和(快速幂)
- 快速矩阵幂+二分等比数列求和
- [noip模拟赛]求和(快速幂)
- python如何实现快速的求和函数
- HDU --- 4686 【矩阵快速幂+求和】
- 等比数列求和 (快速幂 + 逆元)
- 【八中】快速求和(1143 && [CQOI1143])
- Finance系列(2)之Banking
- 171209之JS视频笔记2
- 【Java学习笔记】类之间的关系
- Ciclop开源3D扫描仪软件---Horus源码分析之Image_detection.py
- (13)Spring AOP为目标对象引入新接口
- 快速求和
- 嵌入式Linux开发流程
- CSS(三十二)
- 动态规划入门篇
- CSS(三十三)
- SHA算法Java实现
- Qt struct应用
- 深入理解 Java G1 垃圾收集器
- python2.6.6升级到python2.7.13