题意:乔治有一些同样长的小木棍,他把这些木棍随意地砍成几段,直到每段的长度都不超过50.现在,他想把这些木棍拼成原来的样子,但是却忘记了自己最开始时有多少根木棍和它们的分别长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。例如,若砍完后有4根,长度分别为1,2,3,4,则原来可能是2根长度为5的木棍,也可能是1根长度为10的木棍,其中5是最小可能长度。另一个例子是:砍之后的木棍有9根,长度分别为5,2,1,5,2,1,5,2,1,则最小可能长度为6(5+1=5+1=5+1=2+2+2=6),而不是8(5+2+1=8)。(本段摘自《算法竞赛入门经典(第2版)》)
分析:
本题和POJ 1011以及EOJ 1981的题目一模一样,数据强度为POJ < EOJ < UVa。想法就是迭代加深搜索,去枚举可能木棍的长度,进行搜索。关键在于剪枝。剪枝方法如下:
1. 枚举长度的时候要保证该长度可以被总长度除的尽,否则一定无法组成。
2. 对木棍从大到小排序,可以减少递归深度。
3. 如果当前木棍和前一根木棍长度相同并且前一根没有被使用的话,则说明这一根也不会被使用,直接retrun。
4. 如果当前这一根为组成的第一根却无法拼成的话,直接return,因为每一根木棍都会被使用,如果当前没有使用,那么以后也不会被使用。
5. 如果这一个为组成的最后一根,却也无法完成时,直接return,因为这根放到之后只用只会更差,不会更优。
代码:
#include <iostream>
#include <algorithm>
#include <fstream>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
#include <cctype>
#include <stack>
#include <set>
using namespace std;
const int maxn = 1000 + 5;
bool flag;
int n, sum;
int a[maxn], v[maxn];
bool cmp(const int& x, const int& y)
{
return x > y;
}
bool DFS(int used, int pos, int cur, int limit)
{
if (used == n)
return true;
for (int i = pos; i < n; ++i)
{
if (!v[i])
{
if (cur + a[i] < limit)
{
v[i] = true;
if (DFS(used + 1, i + 1, cur + a[i], limit))
return true;
v[i] = false;
if (cur == 0)
return false;
while (i + 1 < n && a[i] == a[i + 1])
++i;
}
else if (cur + a[i] == limit)
{
v[i] = true;
if (DFS(used + 1, 0, 0, limit))
return true;
v[i] = false;
return false;
}
}
}
return false;
}
int main()
{
while (~scanf("%d", &n), n)
{
sum = 0;
for (int i = 0; i < n; ++i)
{
scanf("%d", &a[i]);
sum += a[i];
}
sort(a, a + n, cmp);
for (int i = n; i > 0; --i)
if (sum % i == 0 && (sum / i) >= a[0])
{
memset(v, 0, sizeof(v));
if (DFS(0, 0, 0, sum / i))
{
printf("%d\n", sum / i);
break;
}
}
}
return 0;
}