经典的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; }