经典的DFS剪枝,值得回味。
多个剪枝:
1.木棍长度L必须整除总长sum;
2.木棍总长L大于等于小木棍最长那个,小于等于sum;
3.小木棍长度排序,从大的开始枚举,因为大的约束多,所以能接近根处剪枝;
4.小木棍排序过,同样长度的相邻,如果有个同样长度的不行,那么之后同样长度的也不行;
5.最重要有效的剪枝:假设木棍长度是L,那么现在是一部分L拼完了,一部分L没拼完如下(分割):
L, L, L, L L, L, L
那么在拼后面第一个L时,A[j]不行,那么由于后面三个L都一样长,是等效的,所以A[j]对后面三个L都不行,就可以直接返回false了。
那么如果这是后面是L',也就是已经拼了一些,那么就和后面不等效,不能剪枝。
L有可能如图出现在第五个而不一定是第一个,是可能因为之前拼了一些后造成的约束条件才导致A[j]在第五个L失败。
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
bool cmp(int a, int b) {
return a > b;
}
// leftLength: 本段木棍还需要拼的长度
// targetLength: 每段木棍的目标长度
// leftCount: 还需要拼出来的木棍
bool sub(int leftLength, int targetLength, int leftCount, int* stick, bool* visit, int n) {
if (leftCount == 0) return true;
if (leftLength == 0) return sub(targetLength, targetLength, leftCount - 1, stick, visit, n); // 拼出了一根木棍,拼下一根
for (int i = 0; i < n; i++) {
if (visit[i] || stick[i] > leftLength)
continue;
if (i > 0 && !visit[i - 1] && stick[i - 1] == stick[i]) continue; // 如果上一根小棍子长度一样且无法拼出,那么
visit[i] = true;
if (sub(leftLength - stick[i], targetLength, leftCount, stick, visit, n)) {
return true;
}
visit[i] = false;
if (leftLength == targetLength) {
return false; // 最有效的剪枝,如果拼L时,A[j]不行,那么后面的未拼的也都是L,等价,所以A[j]永远不行
}
}
return false;
}
int main(void) {
int n;
while(cin >> n && n)
{
bool found = false;
int sum = 0;
int* stick = new int[n];
bool* visit = new bool[n];
for (int i = 0; i < n; i++) {
cin >> stick[i];
sum += stick[i];
visit[i] = false;
}
sort(stick, stick + n, cmp); // 排序,从最长的棍子开始拼,因为越长的棍子约束越大,从最接近根处剪枝
int maxLen = stick[0];
for (int l = maxLen; l <= (sum + 1) / 2; l++) { // 只枚举到sum/2即可,如果还不行,那就是sum了
if (sum % l == 0) { // 必须要能整除
if (sub(l, l, sum / l, stick, visit, n)) {
found = true;
cout << l << endl;
break;
}
}
}
// sum
if (!found) cout << sum << endl;
}
return 0;
}