一、朴素版本
#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;
}