• HDOJ.1342 Lotto (DFS)


    Lotto [从零开始DFS(0)]

    点我挑战题目

    从零开始DFS
    HDOJ.1342 Lotto [从零开始DFS(0)] — DFS思想与框架/双重DFS
    HDOJ.1010 Tempter of the Bone [从零开始DFS(1)] —DFS四向搜索/奇偶剪枝
    HDOJ(HDU).1015 Safecracker [从零开始DFS(2)] —DFS四向搜索变种
    HDOJ(HDU).1016 Prime Ring Problem (DFS) [从零开始DFS(3)] —小结:做DFS题目的关注点
    HDOJ(HDU).1035 Robot Motion [从零开始DFS(4)]—DFS题目练习
    HDOJ(HDU).1241 Oil Deposits(DFS) [从零开始DFS(5)] —DFS八向搜索/双重for循环遍历
    HDOJ(HDU).1258 Sum It Up (DFS) [从零开始DFS(6)] —DFS双重搜索/去重技巧
    HDOJ(HDU).1045 Fire Net [从零开始DFS(7)]—DFS练习/check函数的思想

    题意分析

    给出k(6 < k < 13)个数字,要求从这k个数字中选出升序的6个数字,并且按照字典序输出全部的可能,给出的k个数字已经按照升序排列好。
    乍一看以为是排列组合,怎么想也想不到是用dfs来解决。按照这个数字选或者不选的逻辑,以为是dp什么的。最后看了题解才知道用dfs的方法做,也算是长见识了。作为dfs的第一道题,好好写,纪念一下。
    dfs一般采用递归写法,或许是相比于bfs更加好写吧,所以能用dfs写的都用dfs了。
    既然是深度优先,思路就是沿着一条路一直走,直到走到死胡同,原路返回,返回到有多条道路的地方换其他路走。直到这条支路全部都访问过了,按照原路返回,回到起点,如果起点还有别的支路,那么继续访问,反之结束整个搜索过程。

    这里写图片描述
    (图1-1)

    Tip:数字为访问顺序,红色代表前进的过程,蓝色代表返回的过程。这里可以看到,是永远先访问上边的节点,其次是下面的节点。

    Tiip:故意画成树的样子,树也是一张图呀。

    实际解题的时候不可能无所约束的搜索下去,原因之一是会超时(TLE),原因之二就是没有那个必要。那么就需要减小搜索的规模,俗称剪枝。个人的理解是,当搜索到某一步的时候,继续搜索下去的解,明显不满足题目的要求时,终止这次搜索。

    这里写图片描述
    (图1-2)

    Tip:如图,绿色节点均为不满足题意的解,那么当我搜索到绿色箭头所指向的点的时候,就没必要继续往下搜索了,即后续的3、4、5、6、7、8步骤均为多余的。

    Tiip:由此可见,当数据规模非常大的时候,剪枝可以显著提高程序运行的效率。有时候没剪枝T了,剪枝就AC了。

    回到本题。对于给定数字,面临的选择就是选或者不选,是不是和上面的树逻辑上很相似。先上代码,揉碎了慢慢写。。

    代码总览

    /*
        Title:HDOJ.1342
        Author:pengwill
        Date:2017-2-3
    */
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    int a[20],b[10],n;
    void dfs(int num, int pos)
    {
        if(num == 7){
            for(int i =1 ;i<num; ++i){
                if(i == 1) printf("%d",b[i]);
                else printf(" %d",b[i]);
            }
            printf("
    ");return;
        }
        if(pos>n) return;
        b[num] = a[pos];
        dfs(num+1,pos+1);
        dfs(num,pos+1);
    }
    int main()
    {
        int t = 0;
        while(scanf("%d",&n) && n != 0){
            if(t!=0) printf("
    ");
            for(int i = 1; i<=n; ++i) scanf("%d",&a[i]);
            dfs(1,1);
            t++;
        }
        return 0;
    }
    

    从main函数开始:

    while(scanf("%d",&n) && n != 0){
            if(t!=0) printf("
    ");
            for(int i = 1; i<=n; ++i) scanf("%d",&a[i]);
    
            t++;
        }

    这里是数据的读入部分,题目要求每组数据中间要有空行,所以引入变量t。
    那么关键就在于dfs函数。

    void dfs(int num, int pos)
    {
        if(num == 7){
            for(int i =1 ;i<num; ++i){
                if(i == 1) printf("%d",b[i]);
                else printf(" %d",b[i]);
            }
            printf("
    ");return;
        }
        if(pos>n) return;
        b[num] = a[pos];
        dfs(num+1,pos+1);
        dfs(num,pos+1);
    }

    dfs函数有2个形参,num和pos,乍一看不知道他们的作用,先姑且放着。
    之后是对于num是否为7的一个判断。如果为7的话,进行一个输出,应该就是题目要求的输出,数组b中保存着结果。可见num应该是判断是否构成了6位的排列,当num为7递归调用dfs时,用return语句终止这次搜索。原因很简单,题目只需要我找6位排列数,干嘛找7位的。
    这样的判断,叫做递归边界(如有错误请各位指正)。递归边界可以是判断是否找到了解,如果找到了一组可行的解,就不进行递归了。当然要具体问题具体分析。
    向下看,是对pos是否大于n的判断,如果大于n也就终止搜索了。n表示的是每组数据数字的个数,根据这个也可以想到,应该是从n个数中选6个,如果现在的位置是n+1(数据中根本没有这个数),当然不符合题意,终止。接着是一个赋值语句,应该可以想到是选中a数组pos这个位置的数字,把它写到b的num这个位置。

    下面关键来了:

    dfs(num+1,pos+1);
    dfs(num,pos+1);

    现在已经选中了a数组pos位置的数字,如果选它的话,那么就看下一位置选谁(这个位置是相对于数组b而言的),如果不选这个数字,那么对于这一位置,我们看看a数组下一个数字选还是不选。

    这里写图片描述
    (图1-3)

    Tip:原谅我糟糕的画图技术

    这里写图片描述
    (图1-4)

    如图所示,对于a中某一个数,有选或者不选2中选择(蓝色代表选,红色代表不选),组成了这样一颗树,直到num==7的是,结束搜索。

    由此可以总结出dfs大概的函数模型

    void dfs( 参数 )
    {
        // 递归边界
        // 可以是检查是否满足解的要求
    
        // 完成某系动作
        // 继续递归调用dfs
    }
    

    这里只是皮毛啊,要想深入学习,多做题吧!

    传送门:

    HDOJ.1010 Tempter of the Bone [从零开始DFS(1)]

  • 相关阅读:
    linux环境变量(一)
    linux常用命令-ps
    linux实用小命令--查看文本内容
    postman tests常用方法
    Vue 中嵌套 Iframe,用postMessage通信 踩坑记
    [Vue warn]: Error in nextTick: "RangeError: Maximum call stack size exceeded"
    对STM32所用位带操作宏的详细剖析
    移植Modbus TCP二
    移植Modbus TCP一
    STM32位带操作
  • 原文地址:https://www.cnblogs.com/pengwill/p/7367176.html
Copyright © 2020-2023  润新知