P1120 小木棍

点击查看代码
#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;
}
posted @ 2026-01-08 17:41  AnoSky  阅读(1)  评论(0)    收藏  举报