本题思路非常明确:在所有能插入加号的位置枚举加号是否存在,对于每一种情况,若求得和为n则更新答案。
但是看看数据规模。。。长度<=40,也就是说枚举的时间最多可达2^39,显然会T,所以需要剪枝。
剪枝1:若整串拆分为单个数字后求和,所得结果>n,则一定无解。
原因:显然在一次拆分后,新生成的数字的和不会比未拆分之前的大(不要问我为什么,这是直觉)。
那么对于某个串,它对应的和最小的拆分方案即为:将其拆分为单个数字。
于是乎,整串对应的最小的和都>n,那么就不可能有可行解了。
剪枝2:若将未处理部分作为一个数字与已处理部分相加,所得的和<n,则在此递归子树中一定无解。
原因:类比一下剪枝1,可得:对于某个串,它对应的和最大的拆分方案为:直接将其转化为数字,即不拆分(不要问我为什么,这也是直觉)。
那么,将未处理部分作为一个数字与已处理部分相加,就是此递归子树中对应和最大的方案。
如果最大的和都<n,那么就不可能有可行解了。
剪枝3:若当前加号数量>=ans,则此递归子树中无更优解。这一点十分显然。
剪枝4:若将未处理部分拆分为单个数字后与已处理部分求和,所得的和>n,则在此递归子树中一定无解。
原因:同剪枝1。
剪枝5:若找到一个可行解,则此递归子树中无更优解。再搜下去,加号会增多,一定得不到比此可行解更优的解。
实现细节及剪枝位置详见代码及注释。
1 #include<cstdio> 2 #include<cstring> 3 4 using namespace std; 5 6 void dfs(int,int,int); 7 inline int val(int,int); //将某子串转化为数字 8 inline int min(int,int); 9 inline int qh(int,int); //将某子串拆分为单个数字后求和 10 11 char s[50]; 12 int a[50],l,ans=2147483647,sum=0,n; 13 14 int main(){ 15 scanf("%s",s+1); 16 scanf("%d",&n); 17 l=strlen(s+1); 18 for(int i=1;i<=l;i++){ 19 a[i]=s[i]-'0'; 20 sum+=a[i]; 21 } 22 if(sum>n){ 23 printf("-1 "); //剪枝1 24 return 0; 25 } 26 else{ 27 dfs(0,0,0); 28 if(ans==2147483647)printf("-1 "); 29 else printf("%d ",ans); 30 } 31 32 return 0; 33 } 34 35 void dfs(int res,int p,int dep){ //res:已处理部分的和 p:标记已经处理到何处 dep:加号数量 36 int temp=res+val(p+1,l); //将未处理部分作为一个数字与已处理部分相加 37 if(temp<n)return; //剪枝2 38 if(dep>=ans)return; //剪枝3 39 if(res+qh(p+1,l)>n)return; //剪枝4 40 if(temp==n){ //找到可行解 41 ans=min(ans,dep); //更新答案 42 return; //剪枝5 43 } 44 for(int i=p+1;i<l;i++){ 45 temp=res+val(p+1,i); 46 dfs(temp,i,dep+1); 47 } 48 } 49 50 inline int val(int l,int r){ 51 int ans=0; 52 for(int i=l;i<=r;i++)ans=(ans<<3)+(ans<<1)+a[i]; 53 return ans; 54 } 55 56 inline int min(int x,int y){ 57 if(x<y)return x;else return y; 58 } 59 60 inline int qh(int l,int r){ 61 int ans=0; 62 for(int i=l;i<=r;i++)ans+=a[i]; 63 return ans; 64 }