poj 1011 Java: sticks

本文讨论了在解决POJ1011问题时,如何通过深度优先遍历(DFS)实现基本功能,并在时间限制下,通过剪枝策略优化代码效率。详细解释了剪枝条件的设定,包括充分和必要条件,以及如何通过条件判断提高算法性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述: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;
	}
}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值