DFS

DFS的思想以及实现

      DFS(Depth-First-Search)深度优先搜索算法,是搜索算法的一种。从名字上可以看出,他是优先深入式搜索的,也就是选择一条路进行尝试,不断往后找,如果发现不符合就后退,再去寻找下一条路。

   可以看出,该算法的复杂度是很高的,时间复杂度是O(n!)的阶乘级算法,它的效率比较低,在数据规模变大时,这种算法就显得力不从心了。但是有时候可以进行剪枝操作来去除不必要的操作,或者结合dp等高效的算法来降低时间的消耗。

   其实我们来结合一个题目来更形象的探讨DFS:

 Sticks

George took sticks of the same length and cut them randomly until all parts became at most 50 units long. Now he wants to return sticks to the original state, but he forgot how many sticks he had originally and how long they were originally. Please help him and design a program which computes the smallest possible original length of those sticks. All lengths expressed in units are integers greater than zero.

Input

The input contains blocks of 2 lines. The first line contains the number of sticks parts after cutting, there are at most 64 sticks. The second line contains the lengths of those parts separated by the space. The last line of the file contains zero.

Output

The output should contains the smallest possible length of original sticks, one per line.

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;
}

    

    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值