poj1011,我自己的理解

这是我的第一篇文章,老实说,poj1011,是我第一次意识到我刷题是需要浓厚的算法功底的,前几题是我没学过算法,就根据我看书得来的经验硬写出来的。
原题链接:http://poj.org/problem?id=1011
半年前看见了这道题,我是确实没有任何思路的,看到各路大神说什么dfs,咋也不知道是啥东西。百度了一下,说是深度优先搜索(depth first search),决定了,我得去学算法和数据结构。
说来惭愧,学了半年算法回来重新看这道题还是不会写,我所接触的dfs无非就是二分搜索树的遍历和图论的寻路问题,但是又没有深入去了解图论的知识,所以又偷偷摸摸去看各路大神的代码。原谅我天资愚钝,他们写的理论我是真看不懂,也不知道代码内部原理到底是什么东西。所以我花了整整四天时间,梳理了一下这道题的dfs思想以及优化的关键(他们称之为剪枝。谢天谢地,我的代码终于被AC了。先上代码。

#include<iostream>
#include<algorithm>
using namespace std;
int a[100],Min=0,n,sum;
bool visited[100];
bool cmp(int a,int b){
	return a>b;
}
bool dfs(int index,int cur,int total){
	if(total==Min)
		return true;
	for(int i=index;i<n;i++)
		if(!visited[i]&&a[i]<=cur){
			visited[i]=true;
			if(a[i]==cur){
				if(dfs(1,Min,total-a[i]))
					return true;
			}
			else if(dfs(i+1,cur-a[i],total-a[i]))
				return true;
			visited[i]=false;
			if(a[i]==cur)
				return false;
			if(cur==Min)
				return false;
			while(a[i]==a[i+1])
				i++;
		}
	return false;
}
int main(){
	while(cin>>n&&n!=0){
		sum=0;
		for(int i=0;i<100;i++)
			visited[i]=false;
		for(int i=0;i<n;i++){
			cin>>a[i];
			sum+=a[i];
		}
		sort(a,a+n,cmp);
		for(Min=a[0];Min<=sum;Min++){
			if(sum%Min==0&&dfs(0,Min,sum)){
				cout<<Min<<endl;
				break;		
			}	
		}
	}
}

由于木棒不能重复使用,我建立了一个bool类型全局变量的数组visited来标记是否被用过,false表示没有,true表示用过。a这个数组是记录每根木棒的长度,n是木棒的总个数,sum为木棒长度的总和,Min表示枚举出的长度。我们无法确定木棒的长度到底是多少,所以只能使用枚举法一一枚举,但是这是有方法的。由于乔治拿来的是>=1根的等长木棒并且所有的木棒要么是被切掉分出去的,要么就是原始的长度。所以不难想象,此时枚举出来的长度应当比木棒中最长的长,并且比木棒长度总和要短。同时满足长度和sum%Min==0,或者说sum一定是Min的倍数。
首先应该做的是,对传入的木棒长度进行一次排序,至于为什么要使用从大到小排序,我自己的理解就是若是从小到大排序的话,例如Min为10,假设此时a[0]等于1,那么剩下的长度就要是9,这种情况下排列组合的方式是很多的,但是如果从大到小排序的话,假设a[0]等于8,剩下的长度是2,能相加等于2的情况相比于9来说是要少很多的。这样子做也是程序优化的一种办法。
在dfs这个函数中,一共有三个参数,第一个是index,表示后面迭代的i是从几开始的。第二个是cur,也就是剩下木棒的长度,再次举刚刚的例子,假设a[0]等于8,剩下的长度是2,在调用下一次这个函数时,传入的参数cur就应当是2。第三个参数total表示剩下的总长,其实我感觉这个思想和线段树是十分相似的。递归终止条件就是total与Min相等,此时就说明已经找到了一种方案能够满足所有的木棒都能被visited过。
现在说明一下dfs函数内部的思想,首先迭代的木棒一定是没有被访问过的,也就是!visited[i],其次当前木棒的长度一定要<=cur才能符合要求。换句话说,就是我们想要找的木棒要比剩下的木棒长度短才能符合要求。此时呢,我们当前迭代的木棒就已经被访问过了,也就是visited[i]=true;接下来我们要确定的事情就是,当前木棒的长度是否已经为0了,为0的话我们就要从头开始迭代,并且当前木棒的长度就需要再次为Min了,不是的话我们就需要从i+1开始迭代,这是因为前面的木棒已经迭代过了。木棒的长度为cur-a[i],这里total是需要一直-a[i]的,因为剩下的总长度一直在减少。
对于递归算法,我个人理解为广义的和狭义的,广义下dfs的完成的功能就是没有被visited的木条能否符合Min的条件。而狭义的话我们就需要从他的实现一步步模拟计算(可能需要几张草稿纸)。我们可以这样理解,当前的木棒已经和剩下木棒长度相等时,我们就从广义的方面想没有被visited的木条能否符合Min的条件,如果能就可以直接return true了。不能的话不能直接return false,因为我们的i还需要往下迭代寻找符合的长度。当我们执行到visited[i]=false的时候也就意味着没有return true,换句话说就是当前枚举的i不可行,那么当什么时候我们不需要往下继续枚举直接return false呢?这也是剪枝的重要过程。
第一,a[i]==cur时,为什么呢,因为我们之前已经执行了

			if(a[i]==cur){
				if(dfs(1,Min,total-a[i]))
					return true;

但是并没有return true,换句话说,剩下没有被visited过的木棒不符合条件。我们a[i]==cur时,就可以看成被访问过的木棒已经没了,也就是形成了一个形式一样的子问题。例如:5 5 5 2 2 2 1 1 1,我们将 5和1访问完了后,可以看成此时题目是5 5 2 2 2 1 1的子问题。如果子问题行不通的话,我们总问题下这种枚举的情况是不肯定成立的。
第二,cur=Min时,我们每枚举的第一根木棒时,若在搜索完所有a[]后都无法组合,那么就可以直接return false,这是因为我们每一根木棒都需要用上,当cur=Min,就说明我们此时列举的那根木棒是无法用上的,这例枚举是不可行的。
最后一个优化的思路就是,当下一根木棒和当前木棒长度相等时,由于当前木棒此时枚举不可行,所以下一根也是不可行的。
其实我大多数时间都花在为什么会有枚举不可行却不直接return false的原因的。我想了好久,终于明白若是i迭代过程中,原本和别的木棒组成一根木棒的被当前的木棒抢占了并且正好组合成功,剩下的木棒就无法成功组合了。
接下来,我用我想出来的例子来说明下:10 7 7 7 4 4 3 2 1
这种情况正确的组合应当是10 3 2; 7 7 1; 7 4 4;
但是我们第一次递归的时候10直接流氓一样把4和1抢走了,导致剩下的木棒无法正确合成15。由于下面的递归调用return了0,也就是说明10和4根本不是一对,10再也不敢和4在一起了,就去往下找3,此时才是正确的组合方式。
文章的最后呢,由于我只是一名普通二本的大二学生,我的眼界十分有限,如果我有什么说错的地方,希望你们能够帮我纠正一下错误。感谢你们能够有耐心地看完我的这篇文章,祝愿你们的代码没有bug!

  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值