简单来说就是有很多长度相同的木棍,被切分为不同长度,然后要求重新拼起来,看拼起来之后,长度相同的木棍,最短是多少长度。
这里看到几个问题
- 拼起来之后长度应该是一样的
- 从条件一推断,最段长度minL应该满足total_len % minL == 0,即最小长度可以整除总长度
- 从结论二可以推断,拼好之后最短长度应该大于所有木棍里面最长那根木棍
- 同样从条件一推断,最大长度是所有木棍的总长度,也就是只能拼成一根
- 注意,同样存在一次都不用拼的情况。
- 如果可以拼好,前面的所有木棍都拼完的时候,最后一根木棍是不需要拼的,因为肯定是那么长
然后进一步,为了拼出木棍,有什么好的拼法呢?一个考虑是,将所有木棍按从大到小排序,然后进行拼,这样可以在比较接近根的地方剪掉尽可能的情况。这样就能推理出以下几个剪枝:
- 只需要检查可以被总长度整除的长度
- 只需需要检查大于等于最长单根木棍的长度
- 对于长度等于正在尝试的长度的木棍,直接认为已经拼好,因为不管这根木棍接哪里,都是非法的
- 最后一根木棍不用拼
- 如果有一根木棍,接上去之后刚好构成需要测试的长度,那就应该接上。这是基于以下推理:如果我们换一根更长的,显然,超出我们测试的长度,非法。如果换一根更短的,即使后面这根更短的,能拼出一根相同的长度,其效果也跟使用这根长的一样,并且由于后面更短更灵活,浪费了他补齐的功能。
- 如果在开始拼一根长木棍的时候,第一根拼上去,发现不能继续拼了,应该直接放弃继续拼,因为不论如何,后面都存在无法将这根木棍拼进去的情况。
- 如果某个长度的木棍拼上去,没有可行解,那应该跳过同长度的木棍
#include <stdlib.h>
#include <string.h>
#include <iostream>
using namespace std;
int sticks[70];
bool is_used[70];
bool dfs(int len, int index, int res, int total_len, int checking_len)
{
if(checking_len == total_len)
{
//剩余的总长度跟检查长度相同,这是最后一根了,不用检查,直接成功
return true;
}
for(int i = index; i < len; i++)
{
if(is_used[i])
{
continue;
}
if(sticks[i] > res)
{
//太长,显然剪掉
continue;
}
is_used[i] = true;
if(res == sticks[i])
{
//用掉之后刚好开始拼新一根
if(dfs(len, 0, checking_len, total_len - sticks[i], checking_len))
{
return true;
}
}
//just use it
else if(dfs(len, i, res - sticks[i], total_len - sticks[i], checking_len))
{
return true;
}
is_used[i] = false;
//剪枝:这是第一根拼上来的木棍,说明这个方案不成立,回去吧
if(res == checking_len)
{
return false;
}
//剪枝:不搜索相同长度的棍子
while(sticks[i] == sticks[i+1])i++;
}
return false;
}
int cmp(const void* a, const void* b)
{
return *(int*)b - *(int*)a;
}
int cal(int len, int max_len, int max_one_len)
{
//排序剪枝
qsort(sticks, len, sizeof(int), cmp);
//从单根最长开始尝试
for(int i = max_one_len; i < max_len; i++)
{
if(max_len % i == 0)
{
memset(is_used,0,sizeof(is_used));
//将跟当前尝试长度相同的,直接标志为已用
int stick_total_len = max_len;
for (int j = 0; j < len; ++j) {
if(sticks[j] == i)
{
is_used[j] = true;
stick_total_len -= i;
}
}
//注意有所有木棍都被用完的情况
if(stick_total_len == 0)
{
//all sticks are in same size
return i;
}
//开始测试
if(dfs(len, 0, i, stick_total_len, i))
{
return i;
}
}
}
//最坏情况,所有木棍拼一起
return max_len;
}
int main(int argc, char *argv[])
{
int n = 0 ;
while(1)
{
cin >> n;
if(n == 0)
{
break;
}
memset(sticks, 0, sizeof(sticks));
int max_len = 0;
int max_one_len = 0;
for (int i = 0; i < n; ++i) {
cin >> sticks[i];
max_len += sticks[i];
max_one_len = std::max(max_one_len, sticks[i]);
}
cout << cal(n, max_len, max_one_len) << endl;
}
return 0;
}