DFS的思想以及实现
DFS(Depth-First-Search)深度优先搜索算法,是搜索算法的一种。从名字上可以看出,他是优先深入式搜索的,也就是选择一条路进行尝试,不断往后找,如果发现不符合就后退,再去寻找下一条路。
可以看出,该算法的复杂度是很高的,是时间复杂度是O(n!)的阶乘级算法,它的效率比较低,在数据规模变大时,这种算法就显得力不从心了。但是有时候可以进行剪枝操作来去除不必要的操作,或者结合dp等高效的算法来降低时间的消耗。
其实我们来结合一个题目来更形象的探讨DFS:
Sticks
Input
Output
Sample Input
9 5 2 1 5 2 1 5 2 1 4 1 2 3 4 0
Sample Output
6 5
其实这个题目翻译成中文就是这个意思:
有若干长度相同的棒,把它们切成若干部分,每一部分都是小于50,最多切成m份棒,m<=64,问:求出这些棒原来相同长度的最小值。
也就是给你给你m个数,让你分成n堆,让每一堆的数加起来都相等并且尽可能的小,输出这个最小值。
那我们拿到这个题目,应该能感觉出来这是个深搜题,枚举长度的最小值通过深搜看看能不能符合这个最小值,如果能就输出。
为了搜索的快速,我们把所有小棒的长度由大到小排序。假设用数组a[n]来存放这些长度值。变量sum来记录所有长度的和。那么所求最小长度len满足a[0]<=len<=sum;并且sum%len==0;因为最终要分成x堆,所以x*len==sum,这么取模运算就好理解了。
那我们就从a[0]到sum并满足sum取模等于0的数len枚举就可以了。下一步也是很关键的部分,就是我们怎么判断是否可以分出x组的数使他们的和都等于len呢?这就需要深搜来解决了。每一次我们都是从数组a[0]遍历到a[n-1]。一开始,令res=len从a[0]开始不断往后搜索,如果res>=a[i],标记a[i],res=res-a[i]。一直到整个数组的数都被标记,说明该len就是我们的答案。
以下是示例代码(必要的说明有相应注释):
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
int a[70],n,len,p=0;
bool vis[70];//标记数组用来标记a[i]是否被用过
bool cmp(int a,int b)//使数组由大到小排序
{
return a>b;
}
void DFS(int res,int m)
{
if(p==1)
return ;
if(m==n&&res==0)
{
p=1;
return ;
}
if(res==0&&m<n) {DFS(len,m);}//如果找出一个子序列满足和等于len,就继续找下一组满足的子序列
for(int i=0;i<n;i++)
{
if(a[i]<=res&&!vis[i]&&!p)
{
vis[i]=1;//标记被用过
DFS(res-a[i],m+1);//递归
vis[i]=0;//如果不能构成组合就拿出该元素
if(a[i]==res) {return ;}
if(res==len) {return ;}//这一步至关重要,没有他就被TLE了.讲解一下,这里是说如果剩下的元素不能和a[i]组成len那么就不用继续搜索了,len肯定不符合条件
while(a[i]==a[i+1])//如果a[i]不行,那么和a[i]相等的元素都不行,注意数列是不递减的
i++;
}
}
}
int main()
{
// freopen("s","r",stdin);
while(scanf("%d",&n)!=EOF)
{
if(!n)
break;
int sum=0;
p=0;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
sum+=a[i];
}
sort(a,a+n,cmp);//由大到小排序
if(a[0]>(sum-a[0]))//如果出现这种情况,就不可能再分出和a[0]相等的部分来,所以直接输出sum
{
printf("%d\n",sum);
continue;
}
len=a[0];
while(!p)
{
while(sum%len!=0) {len++;}//len必须满足的一个条件是被sum取模等于0
memset(vis,0,sizeof(vis));//对标记数组赋值,0代表a[i]没有被用
DFS(len,0);//深搜
if(p) {break;}
len++;
}
printf("%d\n",len);
}
return 0;
}