点击查看代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// 全局变量定义
int m = 0; // 碎片数量
int a[70]; // 存放碎片的数组
bool vis[70]; // 标记数组,标记碎片有没有被用掉
int len; // 正在枚举的原始木棒长度
int cnt; // 在当前 len 下,总共需要拼出的木棍根数
// 排序比较函数:从大到小
bool cmp(int x, int y) {
return x > y;
}
//1.状态定义,k当前拼完了几根原始木棒,last上一次处理的碎片是谁,rest这根原始木棒还有多少没拼
bool dfs(int k, int last, int rest) {
//2.截止条件,当前拼完了cnt根木棒,就是最终结果
if (k > cnt) return true;
// 换行逻辑,一根拼完了去找下一根,last要重置
if (rest == 0) return dfs(k + 1, 0, len);
// 【新增优化】利用二分查找快速定位:找到第一个 <= rest 的碎片位置
// 配合 greater<int>() 是因为数组是从大到小排序的
// 这行代码替代了原来的线性扫描起点,极大提升了最后几个测试点的速度
int start = lower_bound(a + last + 1, a + m + 1, rest, greater<int>()) - a;
//3.枚举选择,从 start 开始(而不是 last+1),组合型搜索保证不重复
for (int i = start; i <= m; i++) {
// 枚举选择条件,这个碎片没用过(长度判断已经在 start 处保证了,所以这里只需要判断 vis)
if (!vis[i]) {
vis[i] = true; // 标记使用
//递归:这个和普通搜索型的dfs有点不一样,普通的需要全部搜索,因此不可以这样直接终结,而这里是判定型,只要找到一种方案就可以
if (dfs(k, i, rest - a[i])) return true;
vis[i] = false; //回溯
//三个核心剪枝,这都是基于a[i]失败的情况下做的剪枝
// 剪枝 1:空木棍失败 (Head Pruning)
//这是新木棒的第一步,如果第一个a[i]都失败了那就没必要试了,早晚要用怎么用都失败
if (rest == len) return false;
// 剪枝 2:刚好拼完失败 (Tail Pruning)
//也就是放入a[i]刚好填好了,刚好填好都失败了,那后面的小碎片再拼也没用了
if (rest == a[i]) return false;
// 剪枝 3:跳过重复 (Duplicate Pruning)
//那都一样了还说啥了,包失败的
while (i < m && a[i] == a[i + 1]) i++;
}
}
// 如果试遍了所有碎片都拼不出来,说明这条路是死胡同
return false;
}
//这道题的剪枝也很不一样,放在dfs开头的是可行性剪枝,而放在里面的是最优性/推理剪枝,开头是显微镜,看当前这一步是否可以
//而循环内是望远镜,基于未来失败的结果结合推理做出的剪枝
int main() {
// 优化输入输出速度
ios::sync_with_stdio(false);
cin.tie(0);
int raw_n, val, sum = 0;
cin >> raw_n;
// 1.读取数据并预处理,得到实际有效的小木棒个数以及将碎片的长度放入对应的a数组并求出总长sum
for (int i = 0; i < raw_n; i++) {
cin >> val;
// 【修改】增加题目要求的过滤逻辑,只处理长度 <= 50 的木棍
if (val <= 50) {
m++;
a[m] = val;
sum += val;
}
}
// 2. 排序 (剪枝的基础),1-based
sort(a + 1, a + m + 1, cmp);
// 3. 枚举原始长度 len,从最长碎片的开始,到总长
// 【微调】为了进一步优化,循环上界可以设为 sum / 2,若找不到则答案必定是 sum
for (len = a[1]; len <= sum / 2; len++) {
// 剪枝:必须能整除
if (sum % len != 0) continue;
// 计算目标根数
cnt = sum / len;
// 启动 DFS
// 从第 1 根开始,上一个碎片下标 0(1-based)当前还差 len
if (dfs(1, 0, len)) {
cout << len << endl;
return 0;
}
}
// 如果循环结束没找到(说明不能拆分成2根及以上),那答案就是拼成一根长的
cout << sum << endl;
return 0;
}