5
一道值得做的深搜题目,彰显剪枝的强大。。。
题意就是:刚开始你有若干根长度为K的绳子(棍子、绳子都差不多),然后一个人把这些随机剪掉,
剪成n个短绳子,让你求这些短绳子组合成原来长度的绳子的最短长度。
也可说是:将n个数分成m堆,每一堆的和都相等为H,求H最小为多少。
题中有一些数字:
50:指剪后每一段绳子长度最大为50。
64:指剪后绳子数量最大为64。
还有注意,有可能有绳子根本就没有减。
比如:
5
1 2 3 4 5
最短的就是 1+4=2+3=5
初始就是3根长度为5的绳子,题目没要求求多少根,只求长度最小为多少。
这个题目做法,按照深搜步骤来就行,就是注意一下一些地方的剪枝,
第一次做有个剪枝我也没想到,但就因为那一个枝,让我 一直TLE,后来也是看别人代码看懂的。
主函数中那两个剪枝就不多说明,一看就懂。
主要是dfs函数中那三个if里的剪枝。
首先要明白,什么时候会回溯,就是当dfs进去搜索,发现没有符合的,才会回溯。
也就是说,只有当当前的数不符合的时候才会回溯出来。
第一条剪枝:
如果你剩余的长度和当前长度相等,就没有必要搜索,也就是说当你剩余长度为3,接着搜索3,发现不符合,就不需要搜索剩下能构成3的(比如2+1,1+1+1等)
第二条剪枝:(重要的)
意思是如果剩余的长度等于绳子总长度,而当前不符合,就不需要接着搜索。
也就是说,接下来搜索的长度就是绳子目标长度,而你当前长度根本用不上,那就肯定不符合了。
例子: 假设 要搜索目标长度为 7 :绳长有 7 6 3 2 2.
假设 第一个7 搜索完,接下来搜索6 发现6没有1来配对,程序会接下来搜索 3 2 2,但很明显根本不需要搜索后面的,前面有一个用不上,后面就不需要再搜索了。
第三条剪枝:
如果当前长度的不满足,那么相同长度的就不需要再次去搜索
代码上也有一些解释:
#include <iostream> #include <string.h> #include <algorithm> using namespace std; int n,maxx,sum,arr[70]; bool ispos,vis[70]; // 由大到小排序 bool cmp(int a,int b) { return a>b; } void dfs(int res,int js,int pos) { // 找到答案,返回 if(ispos==1) return; // 如果所有都被选择 ispos置1,表示找到 if(js==n) {ispos=1;return;} // 如果res=0 且并非所有都被选择,则继续将maxx dfs if(res==0 && js<n) dfs(maxx,js,0); int i; for(i=pos;i<n;++i) { if(!ispos && !vis[i] && arr[i]<=res) { vis[i]=1; dfs(res-arr[i],js+1,pos); vis[i]=0; // 这个去掉 +40MS if(res==arr[i]) return; // 这个去掉,TLE。。 if(res==maxx) return; // 这个剪枝去掉了,+100MS while(arr[i]==arr[i+1]) ++i; } } } int main() { int i; while(cin>>n) { if(!n) break; sum=0; for(i=0;i<n;++i) { cin>>arr[i]; sum+=arr[i]; } sort(arr,arr+n,cmp); maxx=arr[0]; // 剪枝:如果最长的长度大于剩余所有长度和,则答案就是他们的和 //(这个去掉了也可以,耗时没有变化 = 。=,就是说不写也可以) if(maxx>sum-maxx) { cout<<sum<<endl; continue; } ispos=0; while(!ispos) { // 剪枝: 如果所有长度总和 对 目标长度 取模不为0 ,则目标长度一定不是答案,这个必须有 // 这个如果去掉,耗时增多不说,对后面剪枝影响,会导致WA while(sum%maxx!=0) {++maxx;} memset(vis,0,sizeof(vis)); dfs(maxx,0,0); if(ispos) break; ++maxx; } cout<<maxx<<endl; } return 0; }