POJ 1011 Sticks (dfs + 厉害的剪枝)

POJ 1011


拿2362的代码来交果断超时,研究了半天也没整出来。看了大神的剪枝方法,简直五体投地。详见代码。


参考博客:http://blog.sina.com.cn/s/blog_7d3ee9f50100wiy4.html


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
bool cmp(int a, int b) {
	return a > b;
}
int n, len;
int s[70], book[70];
int dfs(int left, int num, int start) {	//left表示组成这跟原木棍还需要多少长度
										//num表示还有几根原木棍没拼成
										//start表示从哪根现木棍开始扫 
	if(left == 0) {	
		if(num == 0) return 1;			//搜索退出条件  
		else {
			int i;
			for(i = 0; book[i]; i++);	//剪枝,找到第一个没被使用的现木棍 
			book[i] = 1;			//由于从大到小排序,所以这第一根现木棍一定会被用到,标记 
								//因为假设这次用不到,以后每一根木棍总会被用到 
			if(dfs(len - s[i], num - 1, i + 1)) return 1; 	
									//从第一根没用到过的木棍的下一个开始搜索 
			book[i] = 0;
		}
	}
	else {
		int i;
		for(i = start; i < n; i++) {
			if(i > 0 && s[i] == s[i -1] && book[i - 1] == 0) continue;
										//剪枝,若此木棍和前一个木棍长度相等,
										//而前一个木棍没有被用到,那么这根木棍一定也用不到 
			if(!book[i] && left >= s[i]) {	//简单剪枝,此木棍一定不能大于left 
				book[i] = 1;
				if(dfs(left - s[i], num, i + 1)) return 1;	//忘下一层搜 
				book[i] = 0;
				if(s[i] == left) break;	//一个强大的剪枝, 如果此木棍恰好可以组成这一条原木棍, 
					//但是往下搜索却失败了(不失败的话就会在上一个if语句return,执行不到这一步了)
					//而如果继续往下搜的话,必定要找到其他多条现木棍来代替此木棍填满这根原木棍,
					//但无论找什么代替,代替后的搜索情况一定包含在上一次失败的搜索中了
					//(因为此木棍将用来填补以后的空缺,而这些空缺用可代替的那些木棍来填补也是一样,
					//而这种情况在上次失败的搜索中已经试过了) ,没必要重复,因此退出循环返回上一层 
			}
		}
	}
	return 0;
}
int main() {
	//freopen("input.txt", "r", stdin);
	while(~scanf("%d", &n) && n) {
		int i, sum = 0;
		for(i = 0; i < n; i++) {
			scanf("%d", s + i);
			sum += s[i];
			book[i] = 0;
		}
		sort(s, s + n, cmp);	//降序排列可以提高效率 
		int num;
		for(i = n; i >= 1; i--) {		//i表示原木棍的总数,一定小于n,
										//又因为要找最短长度,所以木棍数量应从大到小数 
			if(sum % i == 0 && sum / i >= s[0]) {	//剪枝1,每根原木棍长度一定是整数
													//并且一定大于等于所有现木棍 
				num = i;					//搜索的初始化 
				memset(book, 0, sizeof(book));
				len = sum / i;
				if(dfs(len, num - 1, 0)) {	//只需要搜n-1根木棍即可 
					printf("%d\n", len);
					break;		//第一个满足条件的便是最小的,此时退出
				}
			}
		}
	}
	return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值