1 条题解

  • 0
    @ 2025-5-4 16:30:51

    算法

    (DFS 剪枝优化) O(nk)O(n^k)

    DFSDFS 搜索顺序:根据木棒的长度从小到大枚举每根木棒,对于每根木棒,枚举可以由哪些木棍拼成,如果所有的木棍拼成了长度相等的多个木棒,说明找到了答案,否则木棒长度加 11 继续搜索。

    为什么是正确的?
    因为题目要求保证拼凑成功的前提下,还有分组尽可能少,即木棒数量尽可能少,所以我们从小到大枚举每根木棒的长度,第一次找到答案时就是最优解。

    剪枝优化:(各种优化,非常多)

    1. 剪枝 1:sum % length == 0 只有 lengthlengthsumsum 的约数才有可能凑出多个等长的木棒

    2. 剪枝 2:优化搜索顺序,木棍长度从大到小排序,可以减少搜索的分支

    3. 排除等效冗余优化
      剪枝 3-1:确定每根木棒中木棍的枚举顺序,因为我们的方案和顺序没有关系,以组合的形式枚举方案可以少搜很多重复方案

    4. 可行性剪枝

      剪枝 4-1:如果当前木棍没有搜到方案,则跳过所有长度相等的木棍
      剪枝 4-2:如果是木棒的第一根木棍就搜索失败了,则一定搜不到方案
      剪枝 4-3:如果是木棒的最后一根木棍(+ 上它木棒长度正好是 length)搜索失败了,也一定搜不到方案

    证明 4-2:如果在当前木棒 aa 的开头换一根木棍 yy 可以搜到方案,那么原来这根木棍 xx一定在后面的某根木棒 bb 中,我们交换木棒 bb 的木棍 xx 和开头的木棍不会影响答案,那么此时就出现了以木棍 xx 开头的木棒搜到了答案,这我们的已知矛盾,所以只要有一根木棒的开头搜不到方案,不管这个位置放哪根木棍都搜不到方案。

    证明 4-3:如果我们换几根 / 一根木棍 yy 放在当前木棒 aa 的最后可以搜到答案,那么原来这跟木棍 xx 也一定在后面的某根木棒 bb 中,因为 xxyy 的长度相等,此时把 xx 交换过去,此时就出现了以木棍 xx 结尾的木棒搜到了答案,这我们的已知矛盾,所以只要有一根木棒的最后一根木棍搜不到方案,不管这个位置放哪根木棍都搜不到方案。

    注意:这里有个前提是,当前木棒 + 最后一根木棍的总长度正好是 length,上面证明才成立。

    具体实现看代码及注释。

    时间复杂度

    O(nk)O(n^k) 指数级别

    空间复杂度

    O(n)O(n)

    C++ 代码

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 70;
    
    int n;
    int length, sum; // length 表示枚举的木棒的长度,sum 表示当前已经拼好的木棒的总长度
    int w[N]; // 每根木棍的长度
    bool st[N]; // 状态数组
    
    // u:当前正在拼哪根木棒,s:当前木棒中已有木棍的总长度,start:从哪个位置开始枚举木棍
    bool dfs(int u, int s, int start)
    {
        if (u * length == sum) return true; // 如果当前木棒的数量 * 长度 = sum,则找到了答案,直接返回 true
        if (s == length) return dfs(u + 1, 0, 0); // 如果当前木棒的长度等于了 length,则新开一根木棒
    
        // 否则枚举从 start 开始枚举将哪根木棍拼到当前木棒中
        for (int i = start; i < n; i ++ ) { // 剪枝 3-1:确定枚举顺序,以组合的形式枚举方案
            if (st[i]) continue; // 如果当前木棍用过了,则跳过
            if (s + w[i] <= length) { // 如果当前木棒拼上该木棍没超过 length
                st[i] = true;
                if (dfs(u, s + w[i], i + 1)) return true; // 继续搜索当前木棒的下一根木棍拼哪根
                st[i] = false;
    
                // 执行到这 dfs(u, s + w[i], i + 1) 为 false: 说明当前木棍搜索失败了
    
                // 剪枝 4-2:如果是木棒的第一根木棍就搜索失败了,则一定搜不到方案
                if (!s) return false; // s = 0 表示刚才木棒拼的是第一根木棍
    
                // 剪枝 4-3:如果是木棒的最后一根木棍搜索失败了,也一定搜不到方案
                if (s + w[i] == length) return false;
    
                // 剪枝 4-1:如果当前木棍没有搜到方案,则跳过所有长度相等的木棍
                int j = i;
                while (j < n && w[i] == w[j]) j ++ ;
                i = j - 1;
            }
        }
    
        return false;
    }
    
    int main()
    {
        while (cin >> n, n) {
            // 多组测试数据清空
            memset(st, 0, sizeof st);
            sum = length =  0;
    
            for (int i = 0; i < n; i ++ ) {
                cin >> w[i];
                if(w[i] > 50) continue;
                sum += w[i];
                length = max(length, w[i]);
            }
    
            // 剪枝 2:优化搜索顺序,木棍长度从大到小排序
            sort(w, w + n);
            reverse(w, w + n);
    		for(int i = 0; i < n; i++)
                if(w[i] > 50)
                    st[i] = true;
            while (true) { // 循环一定会退出,最坏情况下所有木棍拼一根木棒
                if (sum % length == 0 && dfs(0, 0, 0)) { // 剪枝 1:只有 length 是 sum 的约数才有可能凑出多个等长的木棒
                    cout << length << endl;
                    break;
                }
                length ++ ; // 当前 length 无法得到答案,则长度 + 1
            }
        }
    
        return 0;
    }
    
    • 1

    信息

    ID
    101
    时间
    1000ms
    内存
    256MiB
    难度
    8
    标签
    递交数
    62
    已通过
    8
    上传者