我的第一道搜索剪枝的题目。
题意:有一些 木棍,它们的长度相同。现在将木棍折断,最多折成64根。
编程求木棍可能的最小长度。
#include <iostream>
#include <algorithm>
using namespace std;
#define N 70
int stick[N], n;
bool used[N];
bool compare(const int &a, const int &b)
{
return a > b;
}
/*
left为正在拼凑的这根木棍还差多少长度,还需要拼凑的木棍长度,
len为木棍未折断前的长度。
*/
bool Search(int left, int m, int len, int cur)
{
int i;
if(left == 0){
if(m == 2) return true;
/*
当新拼凑出一根木棍后,找到另一根折断后的小木棍作为原木棍的第一部分,
若找不到含有这根小木棍的原木棍,则直接返回false,因为这根小木棍必含
于某根原木棍中,这是一个极其重要的剪枝。
*/
for(cur = 0; used[cur]; cur++);
used[cur] = true;
if( Search(len - stick[cur], m - 1, len, cur + 1) )
return true;
used[cur] = false; //回溯,考虑换成其他方法凑之前凑好的木棍
return false; //找不到以第cur段作为首段的木棍,直接返回false
}
else {
if(cur > n - 1) return false;
/*
注意:这里搜索的都是某根木棍的中间段,因为某段可选也可不选。
*/
for(i = cur; i < n; i++)
{
if(used[i]) continue; //剪枝:某段被使用过了
/*
剪枝:
因为前面那段与当前折断长度相同,而前面那段未被使用,
所以当前这段使用后同样不能凑成一根木棍
*/
if(stick[i-1] == stick[i] && !used[i-1])
continue;
//剪枝:当前段的长度已经大于凑成这根木棍还差的长度了
if(stick[i] > left)
continue;
used[i] = true;
if( Search(left - stick[i], m, len, i + 1) )
return true;
used[i] = false; //回溯,考虑不选当前段作为中间段
}
}
return false;
}
int main()
{
int i, sum;
bool flag;
while(scanf("%d", &n) && n)
{
for(sum = i = 0; i < n; i++)
{
scanf("%d", &stick[i]);
sum += stick[i];
}
/*
将木棍降序排列,因为先把比较长的筷子放入,这样凑成某根筷子的可行性更大。
可以避免很多回溯。
*/
sort(stick, stick + n, compare);
flag = false;
for(i = stick[0]; i <= sum / 2; i++){ //遍历原筷子长度,从最大的一段到总长度的一半
if(sum % i == 0){ //筷子的长度满足被总长度整除
memset(used, false, sizeof(used));
used[0] = true;
if( Search(i - stick[0], sum / i, i, 1) ){
flag = true;
printf("%d\n", i);
break;
}
}
}
if(!flag) printf("%d\n", sum);
}
return 0;
}