一、深入理解DFS

  • 采用递归写法
  • 深度优先,思路就是沿着一条路一直走,直到走到死胡同,原路返回,返回到有多条道路的地方换其他路走。直到这条支路全部都访问过了,按照原路返回,回到起点,如果起点还有别的支路,那么继续访问,反之结束整个搜索过程。
  • 实际解题的时候不可能无所约束的搜索下去,原因之一是会超时(TLE),原因之二就是没有那个必要。那么就需要减小搜索的规模,俗称剪枝。个人的理解是,当搜索到某一步的时候,继续搜索下去的解,明显不满足题目的要求时,终止这次搜索。

下面用一张图来加深理解:

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

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

二、例题讲解——hdu1342

题意:给出k(6 < k < 13)个数字,要求从这k个数字中选出升序的6个数字,并且按照字典序输出全部的可能,给出的k个数字已经按照升序排列好。

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

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int a[],b[],n;
void dfs(int num, int pos)
{
if(num == ){
for(int i = ;i<num; ++i){
if(i == ) printf("%d",b[i]);
else printf(" %d",b[i]);
}
printf("\n");return;
}
if(pos>n) return;
b[num] = a[pos];
dfs(num+,pos+);
dfs(num,pos+);
}
int main()
{
int t = ;
while(scanf("%d",&n) && n != ){
if(t!=) printf("\n");
for(int i = ; i<=n; ++i) scanf("%d",&a[i]);
dfs(,);
t++;
}
return ;
}

从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数组下一个数字选还是不选。

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

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

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

三、例题讲解——poj1011

题意:给出一定数量的小木棒的长度,它是由等长的若干木棒随意砍断所得到的。对于给定的一组小木棒,请求出原始木棒的最小长度。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int a[];
int n; //n:n根小棍子
int vis[]; //记某一小棒在当前状态下是否已经被用于组合原棒 bool cmp(const int a,const int b)
{
return a>b;
} int dfs(int len,int rest,int num) //len:要拼成的大棍子的长度,rest:还要多长 ,num:可用的小棍子数
{
//rest==0&&num==0说明能用的棒已经没有,而且拼成一根原棒还需的长度为0,这就表示原棒已经完整的由这所有的小棒拼接出来。
//此时只需返回len(试探成功)
if(rest== && num==){
return len;
}
//当rest减小到0时,说明一根大木棍拼接完成,它将重新被赋值为len,从而进行下一根大木棍的搜索
if(rest==){
rest = len;
}
for(int i=;i<=n;i++){
if(!vis[i] && rest>=a[i]){
vis[i] = ;
if(dfs(len,rest-a[i],num-)){
return len;
}
//此条路不通,标记取消
vis[i] = ; //当换一个原木棒长度进行试探时,要置vis为0,否则会与上次搜索混在一起
if(a[i]==rest || len==rest){
break;
}
//跳过重复长度的木棍,当前木棍跟它后面木棍的无法得出合法解,后面跟它一样长度的木棍也不可能得到合法解
//因为后面相同长度木棍能做到的,前面这根木棍也能做到。
while(a[i]==a[i+]){
i++;
}
}
}
return ; //深搜完成,仍未返回试探成功,到了函数的最后,只能说明这个试探失败。直接返回0
} int main()
{
while(cin>>n && !(n==)){
int ans=,sum = ; //sum:所有小棒长度之和
for(int i=;i<=n;i++){
scanf("%d",&a[i]);
sum += a[i];
}
sort(a+,a++n,cmp); //剪枝:从大到小排序后可大大减少递归次数
//原木棒长度的取值范围为[a[1],sum]
//枚举这个区间内的数,要满足sum%len==0
for(int len=a[];len<=sum;len++){
memset(vis,,sizeof(vis));
if(sum%len==){
ans = dfs(len,,n);
if(ans) break;
}
}
printf("%d\n",ans);
}
return ;
}

【dfs形参】

  1. len:当前要拼成的原始木棒长度
  2. rest:当前选用的小木棒的长度之和距离len还缺rest (当它减小到0时,说明一根原始木棒拼接完成,它将重新被赋值为len,从而进行下一根木棒的搜索)
  3. num:当前可用的小木棒的数量

【剪枝】

  • 将所有题目给的小木棒的长度按照从大到小的顺序排列,这样最长的小木棒为a[1],最短的小木棒为a[1+n];
  • 原始木棒的长度的取值范围为[ a[1],sum ]中;(sum为小棍子的总长度)
  • 当前木棒跟它后面木棒无法得出合法解,后面跟它一样长度的木棒也不可能得到合法解,因为后面相同长度木棒能做到的,前面这根木棒也能做到。

【重要思想】

由于过程中要确定某根小棒是否确定成功被接收,它就得提前预知加入这根小棒后其它的小木棒能不能匹配成功,就叫要求在遍历某个小木棒时,对其后的木棒进行递归搜索(深搜的特点)。

若能匹配成功,则标记当前小木棒为用过,可以直接返回(试探成功);若不能匹配,说明此棒目前不可用,将标记取消,待下一次搜索用。

若当前木棒不可用,那么与这根小木棒长度相同的木棒也将不可用,直接跳过(剪枝),而且若这个小木棒的长度刚好是rest的长度,那么更能说明后面的不能匹配了,因为如此合适的小棒被接收都不能导至试探成功,后面的小棒更不可能,直接返回0(试探失败 )(剪枝)。还有就是如果len=rest(说明这是新一根原棒,还没有进行匹配),而在预先判断匹配与否时已经判断不能匹配,这样都不能匹配,那么说明以后都不能匹配了(这就是深搜的效果了)。返回0(试探失败)(剪枝)。

【他家之言】

  • 如果当前木棍能恰好填满一根原始木棍,但因剩余的木棍无法组合出合法解而返回,那么让我们考虑接下来的两种策略,一是用更长的木棍来代替当前木棍,显然这样总长度会超过原始木棍的长度,违法。二是用更短的木棍组合来代替这根木棍,他们的总长恰好是当前木棍的长度,但是由于这些替代木棍在后面的搜索中无法得到合法解,当前木棍也不可能替代这些木棍组合出合法解。因为当前木棍的做的事这些替代木棍也能做到。所以,当出现加上某根木棍恰好能填满一根原始木棍,但又在后面的搜索中失败了,就不必考虑其他木棍了,直接退出当前的枚举。
  • 考虑每根原始木棍的第一根木棍,如果当前枚举的木棍长度无法得出合法解,就不必考虑下一根木棍了,当前木棍一定是作为某根原始木棍的第一根木棍的,现在不行,以后也不可能得出合法解。也就是说每根原始木棍的第一根小木棍一定要成功,否则就返回。

DFS做题小结的更多相关文章

  1. 【做题】POJ3469 Dual Core CPU——第一道网络流

    刚学了Dinic就开始做题,然后就崩了. 题意:若干个任务,可以放在两个CPU中任意一个上完成,各有一定代价.其中又有若干对任务,如果它们不在同一个CPU上完成,会产生额外代价.最小化并输出代价. 一 ...

  2. [日记&做题记录]-Noip2016提高组复赛 倒数十天

    写这篇博客的时候有点激动 为了让自己不颓 还是写写日记 存存模板 Nov.8 2016 今天早上买了两个蛋挞 吃了一个 然后就做数论(前天晚上还是想放弃数论 但是昨天被数论虐了 woc noip模拟赛 ...

  3. SDOI2016 R1做题笔记

    SDOI2016 R1做题笔记 经过很久很久的时间,shzr终于做完了SDOI2016一轮的题目. 其实没想到竟然是2016年的题目先做完,因为14年的六个题很早就做了四个了,但是后两个有点开不动.. ...

  4. poj1564 Sum It Up dfs水题

    题目描述: Description Given a specified total t and a list of n integers, find all distinct sums using n ...

  5. AtCoder Grand Contest 11~17 做题小记

    原文链接https://www.cnblogs.com/zhouzhendong/p/AtCoder-Grand-Contest-from-11-to-20.html UPD(2018-11-16): ...

  6. AtCoder Grand Contest 1~10 做题小记

    原文链接https://www.cnblogs.com/zhouzhendong/p/AtCoder-Grand-Contest-from-1-to-10.html 考虑到博客内容较多,编辑不方便的情 ...

  7. 【做题】BZOJ2342 双倍回文——马拉车&并查集

    题意:有一个长度为\(n\)的字符串,求它最长的子串\(s\)满足\(s\)是长度为4的倍数的回文串,且它的前半部分和后半部分都是回文串. \(n \leq 5 \times 10^5\) 首先,显然 ...

  8. 【做题】spoj4060 A game with probability——dp

    赛前做题时忽然发现自己概率博弈类dp很弱,心好慌.(获胜概率或最优解期望) 于是就做了这道题,续了特别久. 一开始列dp式子的时候就花了很长时间,首先搞错了两次,然后忘记了根据上一轮dp值直接确定选什 ...

  9. noip做题记录+挑战一句话题解?

    因为灵巧实在太弱辽不得不做点noip续下命QQAQQQ 2018 积木大赛/铺设道路 傻逼原题? 然后傻逼的我居然检查了半天是不是有陷阱最后花了差不多一个小时才做掉我做过的原题...真的傻逼了我:( ...

随机推荐

  1. 如何用Redlock实现分布式锁

    转载请标明出处: http://blog.csdn.net/forezp/article/details/70305336 本文出自方志朋的博客 之前写过一篇文章<如何在springcloud分 ...

  2. datagrid中设置编辑,删除列是否可以访问

    foreach (RepeaterItem Item in rpt_Result.Items) { LinkButton edit = (LinkButton)Item.FindControl(&qu ...

  3. Struts2中期(这框架目前正处于淘汰状态)

    Struts2的第二天 Struts2的第二天的内容 1. Struts2框架中的Servlet的API的使用 2. Struts2中Action接收请求参数 3. Struts2中自定义拦截器 案例 ...

  4. Java入门(一)

    一.语言分类 机器语言 汇编语言 高级语言 二.Java分类 JavaSE 标准版,主要针对桌面应用 JavaEE 企业版,主要针对服务器端的应用 JavaME 微型版,主要针对消费性电子产品的应用 ...

  5. 在mac上显示网速的软件——iStat Menus 5:

    在mac上显示网速的软件——iStat Menus 5: https://bjango.com/mac/istatmenus/ 注册码: Email: 982092332@qq.com SN: GAW ...

  6. 编译安装GCC

    下载GCC包 http://mirror.hust.edu.cn/gnu/gcc/ 解压 .tar.gzcd gcc-4.9.4./contrib/download_prerequisites #下载 ...

  7. 【解决】MongoDB 线上业务处理,数据去重脚本实现

    mongo客户端工具下载  https://robomongo.org/download   线上业务,k线 展示出现问题,相同时间戳的数据多次插入导致数据不真实,后经排查发现是每次都是写的四条数据, ...

  8. Layabox进阶之资源加载

    资源加载失败,图片资源默认类型是image 如果是sprite可能找不到. 资源的加载顺序,场景被加载出来时,要判断该场景的资源是否都已经加载到. 点击A界面弹出来B界面,A界面的资源要在B界面之前加 ...

  9. 如何理解NaN?

    NaN这个特殊的Number与所有其他值都不相等,包括它自己:   NaN===NaN:  //false   唯一能判断NaN的方法是通过isNaN()函数:   isNaN(NaN);  //tr ...

  10. jquery之prop与attr区别。

    一切看下面代码示例<!DOCTYPE html> <html> <head> <title>全选和反选</title> <script ...