题目描述:http://poj.org/problem?id=1011
本题考察的内容是深度优先遍历(DFS),要实现其要求的输出并不困难。但是题目存在时间限制,仅仅使用DFS结果会超时。因此需要我们添加一些判断拼凑成功和不成功的条件,也就是所谓的“剪枝”。
我在“剪枝”过程中遇到困难,查阅网络资料后解决了问题。在这过程中,很大程度借鉴了这篇博客的内容,也可以看做是对其的解读。
首先我们来看一下最开始在没有“剪枝”的条件下的代码:
<span style="font-size:10px;">import java.util.*;
public class Poj1011_uncut {
static boolean[] used;
static int num;//分割后木棍的数量
static int[] s;
static int sum;
static int max;
static int parts;
//"木棒"表示合成后的stick,"木棍"表示未合成的stick
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while((num = sc.nextInt()) != 0){
used = new boolean[num];
sum = 0;
s = new int[num];
for(int i = 0; i<num; i++){
s[i] = sc.nextInt();
sum += s[i];
}
Arrays.sort(s);//对木棍进行排序(从小到大)
max = s[num - 1];
//木棒的长度一定大于等于最长的木棍长度,所以从最长的木棍开始
for(; max<= sum; max++){
//木棒的长度一定能被总长度整除
if(sum%max == 0){
parts = sum/max;//木棒的数目
if(search(0, num - 1, 0)){
System.out.println(max);
break;
}
}
}
}
sc.close();
}
/*
* 搜索能拼成一个木棒的木棍
* @param res:当前这根木棒已有的长度
* @param next:下一个搜索的木棍的下标
* @param cpl:已经拼成的木棒数量
*/
public static boolean search(int res, int next, int cpl){
//当res=max时,本次合成成功。
if(res == max){
cpl++;
res = 0;
next = num - 2;
}
//cpl = parts,当前所有木棒合成完毕
if(cpl == parts){
return true;
}
//没有成功,继续合成
while(next >= 0){
//如果当前木棍没有被用过
if(used[next] == false){
//木棒的当前长度+当前搜索的木棍长度 没有超过max,则可以放入
if(res + s[next] <= max){
used[next] = true;
//继续搜索成功
if(search(res + s[next], next - 1, cpl)){
return true;
}
//搜索不成功
used[next] = false;
}
}
next--;
}
return false;
}
}</span><span style="font-size: 14px;">
</span>
这样的代码是无法符合运行时间要求的。那么我们接下来看看,有哪些条件是我们可以用来“剪枝”的。
第一类条件是正确解的充分条件:
1、木棒的长度一定大于等于最长的一根木棍的长度。
2、木棒的长度,一定是总长度的约数。
可以发现其实在上面为通过的代码中,也已经使用了这两个条件。
第二类条件是非正确解的必要条件:
1、若某次搜索中,当前最长的木棍匹配失败,这当前木棒的长度一定是非正确解。
2、若某次搜索中,当前木棍拼凑成功,但是剩下的无法拼凑成功,则此次匹配也一定失败。
3、若当前剩余未使用的木棍加起来都无法小于木棒的长度,则此次匹配也一定失败。
还有第三类条件,可以使匹配更加高效:
1、如果某一个木棍匹配失败,则在当前条件下,与其相同长度的木棍都不用在匹配了。
有了以上的条件,我们就可以对之前的代码进行“剪枝”处理:
import java.util.*;
public class Poj1011_cut {
static boolean[] used;
static int num;//分割后木棍的数量
static int[] s;
static int sum;
static int max;
static int parts;
//"木棒"表示合成后的stick,"木棍"表示未合成的stick
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while((num = sc.nextInt()) != 0){
used = new boolean[num];
sum = 0;
s = new int[num];
for(int i = 0; i<num; i++){
s[i] = sc.nextInt();
sum += s[i];
}
Arrays.sort(s);//对木棍进行排序(从小到大)
max = s[num - 1];
//木棒的长度一定大于等于最长的木棍长度,所以从最长的木棍开始
for(; max<= sum; max++){
//木棒的长度一定能被总长度整除
if(sum%max == 0){
parts = sum/max;//木棒的数目
if(search(0, num - 1, 0)){
System.out.println(max);
break;
}
}
}
}
sc.close();
}
/*
* 搜索能拼成一个木棒的木棍
* @param res:当前这根木棒已有的长度
* @param next:下一个搜索的木棍的下标
* @param cpl:已经拼成的木棒数量
*/
public static boolean search(int res, int next, int cpl){
//当res=max时,本次合成成功。
if(res == max){
cpl++;
res = 0;
next = num - 1;
//当一次合成成功后,next可能指在数组的任意位置,此时应该将其置回,继续从当前最大的开始合成
}
//cpl = parts,当前所有木棒合成完毕
if(cpl == parts){
return true;
}
//没有成功,继续合成
while(next >= 0){
//如果当前木棍没有被用过
if(used[next] == false){
//木棒的当前长度+当前搜索的木棍长度 没有超过max,则可以放入
if(res + s[next] <= max){
used[next] = true;
//继续搜索成功
if(search(res + s[next], next - 1, cpl)){
return true;
}
//搜索不成功
used[next] = false;
//若本次搜索失败时,res=0,说明当前最长木棍无法合成木棒,则肯定会搜索失败
if(res == 0){
break;
}
//可以合成当前的,但是剩下的无法合成
if(res + s[next] == max){
break;
}
}
//如果某一个木棍匹配失败,则在当前条件下,与其相同长度的木棍都不用在匹配了
int i = next - 1;
while(i >=0 && s[i] == s[next]){
i--;
}
next = i;
//计算剩余木棍的总长度
int l_s = 0;
while(i >= 0){
if(used[i] == false){
l_s += s[i];
}
i--;
}
//如果剩余木棍的总长度都小于max-res,则拼凑一定失败
if(l_s < max - res){
break;
}
continue;
}
next--;//此处用于控制当if(used[next] == false)不满足时,指向下一个木棍
}
return false;
}
}