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("\n");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("\n");
        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("\n");
        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("\n");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)]

posted @ 2017-02-03 23:17  pengwill  阅读(156)  评论(0编辑  收藏  举报