AcWing 167. 木棒


一、朴素版本

#include 

using namespace std;
const int N = 65;
int s[N], n, len, sum;
bool st[N];

// u:正在填充第几个木棒
// res:正在填充的,最后一个木棒,目前的长度是多少
bool dfs(int res, int u) {
    //因为填充的逻辑是填充满了一个,才能走到下一个面前,所以如果成功到达了第u个前面的话,说明前u-1个都是填充满的
    //如果在第u个面前,检查到木棒长度 乘以 木棒数量 等于总长度,说明完成了所有填充工作,递归终止
    if (len * u == sum) return true;

    //如果当前填充的木棒长度达到本次尝试的木棒长度时,开一个新木棒
    if (res == len) return dfs(0, u + 1); //开新木棒时,此木棒的内部长度为零

    //如果没有填充满,那么枚举每个木棍,找出没用过的,尝试放进去
    for (int i = 0; i < n; i++) {
        if (st[i] || res + s[i] > len) continue; //如果超过木棒的长度规定限制,就不能放进去
        //我放
        st[i] = true;
        //在我放进去后,能不能成功完成填充任务,就不依赖于我的决策了,而是依赖于后续场景的报告
        if (dfs(res + s[i], u)) return true;
        //我取
        st[i] = false;
    }
    return false;
}
int main() {
//配置从文本文件读入数据
#ifndef ONLINE_JUDGE
    freopen("167.txt", "r", stdin);
#endif

    while (cin >> n && n) {
        //初始化
        sum = 0;
        memset(st, false, sizeof st);

        //读入数据,并计算总长度
        for (int i = 0; i < n; i++) cin >> s[i], sum += s[i];

        //枚举每个可能的长度
        for (len = 1; len <= sum; len++) {
            if (dfs(0, 1)) { //在可能长度是len的情况下,开始进行深度优先搜索
                //找出答案,输出结果
                cout << len << endl;
                break;
            }
        }
    }
    return 0;
}

二、减枝优化

#include 
using namespace std;

const int N = 65;
int s[N];   //用来装每个木棍的长度
int n;      //木棍个数
int len;    //组长度
int sum;    //总长度
bool st[N]; //标识某个木棍是不是使用过了

/*
start:从哪个索引号开始继续查找
res:最后一个木棒目前的长度
u:木棒号,下标从0开始

只要搜索到解就停止的dfs问题,一般用bool类型作为返回值,因为这样,搜到最优解就返回true,用dfs的返回值作为条件,
就可以瞬间退出递归了。比上一题中专门设置标志变量的方法要更好。
*/
bool dfs(int start, int res, int u) {
    //因为填充的逻辑是填充满了一个,才能走到下一个面前,所以如果成功到达了第u个前面的话,说明前u-1个都是填充满的
    //如果在第u个面前,检查到木棒长度 乘以 木棒数量 等于总长度,说明完成了所有填充工作,递归终止
    if (len * u == sum) return true;

    //因为每一个木棒原来都是客观存在的,所以,每组木棍必须可以填充满一个木棒
    //不能填充满,就不能继续填充下一个木棒
    if (res == len) return dfs(0, 0, u + 1); // 注意当一组完成时,下一组从0开始搜

    //在当前木棒没有填充满的情况下,需要继续找某个木棍进行填充
    // start表示搜索开始的位置
    //防止出现 abc ,cba这样的情况,我们要的是组合,不要排列,每次查找选择元素后面的就可以了
    for (int i = start; i < n; i++) {
        //使用过 or 超过枚举的木棒长度
        if (st[i] || res + s[i] > len) continue;

        //准备将i号木棍,放入u这个木棒中
        st[i] = true;                                   //标识i号木棍已使用过
        if (dfs(start + 1, res + s[i], u)) return true; //将i号木棍放入u号木棒中
        st[i] = false;                                  //恢复现场

        //可行性剪枝
        //优化3:如果在第u组放置失败,且此时第u组长度为0,说明存在有的木棒长度大于我们设定的长度,所以这个长度一定不可行,直接false
        //优化4:如果加入一个元素后某个分组和等于len了,但是后续搜索失败了(后续肯定是开新组并且res从0开始),则没有可行解,和3是等价的。
        if (!res || res + s[i] == len) return false;

        //优化5:冗余性剪枝
        //如果当前未放置成功,则后面和该木棒长度相等的也一样,直接略过即可
        while (i < n - 1 && s[i] == s[i + 1]) i++;
    }
    return false;
}
int main() {
//配置从文本文件读入数据
#ifndef ONLINE_JUDGE
    freopen("167.txt", "r", stdin);
#endif

    //多组测试数据,以0结束输入
    while (cin >> n && n) {
        //多组数组初始化
        sum = 0;                      //木棒总长清零
        int m = 0;                    //本输的木棍最大长度,枚举长度的起始值
        memset(st, false, sizeof st); //清空使用状态桶数组

        //录入n个木棍的长度
        for (int i = 0; i < n; i++) {
            cin >> s[i];
            m = max(m, s[i]); //找出最长的木棍
            sum += s[i];      //总长度
        }

        //优化1:按小猫思路,由大到小排序,因为大卡车越往前越好找空放,越往后越不容易找空
        //根据搜索顺序优化,枚举长度小的分支比长度大的分支要多,所以先枚举长度大的
        sort(s, s + n, greater());

        //枚举每一个可能的木棒长度,注意这是一个全局量,因为需要控制递归的出口
        for (len = m; len <= sum; len++) {
            //优化2:如果总长度不能整除木棒长度,那么不能按这个长度设置木棒长度
            if (sum % len == 0 && dfs(0, 0, 1)) {
                cout << len << endl; //找到最短的木棒长度
                break;               //找到一个就退出
            }
        }
    }
    return 0;
}

相关