Poj 1011-Sticks
题意:有n根木棍(n<=64),它们由一些相同长度的木棍切割而来,给定这n根木棍的长度,求使得原来长度可能的最小值。
分析:这题不愧是经典中的经典,很早之前就写过,先后2次都没写出来,然后就放了一段时间,这是第三次了,终于写出来了。
基本思路是我们计算出出所有的木棍长度的长度和sum,从小到大枚举它的约数,找到第一个可行解就行了。
具体实现的话:首先,木棍有64根,搜索树是非常巨大的,如果不加任何剪枝,程序根本运行不出来,所以必须进行剪枝。
之前的处理办法是对于当前的木棍,搜索能与之搭配成功的木棍并标记,下次就不要考虑了,然而,这个方法是错误的,因为有时某根木棍能搭配的方法有多种,但是只有一种是可行的,而标记之后就错了。所以对于当前选取的第一个木棍,如果搭配成功了,但是没有得出解,则还是需要继续考虑不选取这些木棍的搜索。
剪枝的策略:
1、搜索的顺序的选取:首先对木棍做一次排序的的处理,越短的木棍越灵活,所以按木棍长度从大到小开始搜索,这样可以尽可能地剪枝。另外一点是排序之后也利于之后的可行性剪枝。
2、可行性剪枝:(1)、每次选定一根木棍去搭配之后,如果当前的木棍无法得出解,那么之后相同长度的木棍便不需要在重复搜索了,因为这也必定不可能得出解。
(2)、需要明确的一点是每根木棍一定是原来的一部分,这样的话如果选择某一根木棍开始搜索时,未能成功地得出解,那么没必要在进行后面的搜索了,直接返回即可。所以搜索时分开处理,如果是从0开始选取的第一根木棍,不成功就直接返回。
只需要应用上面的两个可行性剪枝,效率便可以大大提高,从而在规定时间内求出解。题目只需要求出可行解,而不是最优解,所以就不存在最优性剪枝了。
总结:搜索作为一个“万能算法”,虽说大部分情况可行,但是效率低下,一般搜索都要考虑到剪枝,而剪枝的技巧就很多了,首先考虑搜索的顺序,之后考虑两种方式的剪枝:可行性剪枝和最优性剪枝。可以多看看集训队的论文,总结全面而且总结的很好,但最重要的还是多实践,这样才能真正掌握其思想。
#include <cstdio>
#include <algorithm>
#include <functional>
#include <cstring>
using namespace std;
const int N = 70;
int a[N], vis[N];
int num, len, n;
bool dfs(int l, int cnt, int x) {
if (cnt == num) return 1;
if (l == 0) {//从0开始选择的第一根木棍,搜索失败直接返回
int k = 0;
while (vis[k]) k++;
vis[k] = 1;
if (dfs(a[k], cnt, k+1)) return 1;
vis[k] = 0;
return 0;
}
if (l == len) return dfs(0, cnt+1, 0);
for (int i = x; i < n; ) {
if (!vis[i] && l + a[i] <= len) {
vis[i] = 1;
if (dfs(l+a[i], cnt, i+1)) return 1;
vis[i] = 0;
int t = a[i];
while (i < n && a[i] == t) i++;//相同长度的木棍不重复进行搜索
}
else i++;
}
return 0;
}
int main() {
while (scanf("%d", &n), n) {
int sum = 0, flag = 1;
for (int i = 0; i < n; i++) scanf("%d", a+i), sum += a[i];
sort(a, a+n, greater<int>());
int t = sum/2;
for (len = a[0]; len <= t && flag; len++) {
if (sum % len == 0) {
memset(vis, 0, sizeof(int)*n);
num = sum/len;
if (dfs(0, 0, 0)) { flag = 0; break; }
}
}
printf("%d\n", flag ? sum : len);
}
return 0;
}