先看题目:https://ac.nowcoder.com/acm/problem/50243
题目描述:
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
解题思路:
这道题n不超过64,普通dfs会不行。
需要优化搜索,核心是减少搜索树的大小。方法是:1.改变搜索顺序 2.剪枝:最优化剪枝&可行性剪枝。
1.枚举长度l,满足sum % l == 0
2.将题目给的棍子的长度按照从大到小顺序排列,然后按此顺序进行深搜。为什么先拼长的呢?我们希望这个树在下面的层分叉多,而不是在上面的层分叉多。大的一放,如果不合法了很快就被搞掉了。
3.思考如果棍子放在了要拼长度的第一个位置不合法了,那么还有必要继续吗?既然拼到第一个位置都不合法,那拼到后面任何一个位置也肯定都不合法。同样的还有就是,拼在要拼长度的最后一个位置,因为我们是从大到小的顺序,那么如果大的拼上后,后面不合法了,那么如果换成小的拼成了这块长度,后面的灵活性还不如刚刚,因此也不用继续了。
4.如果一个长度不合法了,那么与它相同的长度也都不用继续了
5.因为顺序从大到小,加了个now标记当前枚举到哪个小木棍,再dfs的时候从now+1开始就行。
总结:剪枝一定要多心思心思,有些看起来无关紧要的实际上能减少大规模的搜索树。
代码:
#include<bits/stdc++.h>
using namespace std;
int n, sum;
int a[70];
bool vis[70];
bool cmp(int x, int y) {
return x > y;
}
bool dfs(int num, int len, int rest, int now)
{
if((num == 0) && (rest == 0)) return true; //一开始只写了num == 0 ,还应该rest == 0
if(rest == 0) rest = len , now = 0;
for(int i = now; i < n; i++) {
if(vis[i]) continue;
if(a[i] > rest) continue;
vis[i] = 1;
if(dfs(num-1, len, rest-a[i], i+1)) return true;
vis[i] = 0;
if((a[i] == rest) || (rest == len)) break;
while(a[i] == a[i+1]) i++;
}
return false;
}
int main()
{
while(~scanf("%d",&n) && n!=0) {
sum=0;
memset(vis,0,sizeof(vis));
for(int i = 0; i < n; i++) {
scanf("%d",&a[i]);
sum += a[i];
}
sort(a, a+n, cmp);
for(int l = a[0]; l <= sum; l++) {
if(sum%l != 0) continue;
if(dfs(n, l, l, 0)) {
printf("%d\n",l);
break;
}
}
}
}

京公网安备 11010502036488号