DFS做题小结
一、深入理解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形参】
- len:当前要拼成的原始木棒长度
- rest:当前选用的小木棒的长度之和距离len还缺rest (当它减小到0时,说明一根原始木棒拼接完成,它将重新被赋值为len,从而进行下一根木棒的搜索)
- num:当前可用的小木棒的数量
【剪枝】
- 将所有题目给的小木棒的长度按照从大到小的顺序排列,这样最长的小木棒为a[1],最短的小木棒为a[1+n];
- 原始木棒的长度的取值范围为[ a[1],sum ]中;(sum为小棍子的总长度)
- 当前木棒跟它后面木棒无法得出合法解,后面跟它一样长度的木棒也不可能得到合法解,因为后面相同长度木棒能做到的,前面这根木棒也能做到。
【重要思想】
由于过程中要确定某根小棒是否确定成功被接收,它就得提前预知加入这根小棒后其它的小木棒能不能匹配成功,就叫要求在遍历某个小木棒时,对其后的木棒进行递归搜索(深搜的特点)。
若能匹配成功,则标记当前小木棒为用过,可以直接返回(试探成功);若不能匹配,说明此棒目前不可用,将标记取消,待下一次搜索用。
若当前木棒不可用,那么与这根小木棒长度相同的木棒也将不可用,直接跳过(剪枝),而且若这个小木棒的长度刚好是rest的长度,那么更能说明后面的不能匹配了,因为如此合适的小棒被接收都不能导至试探成功,后面的小棒更不可能,直接返回0(试探失败 )(剪枝)。还有就是如果len=rest(说明这是新一根原棒,还没有进行匹配),而在预先判断匹配与否时已经判断不能匹配,这样都不能匹配,那么说明以后都不能匹配了(这就是深搜的效果了)。返回0(试探失败)(剪枝)。
【他家之言】
- 如果当前木棍能恰好填满一根原始木棍,但因剩余的木棍无法组合出合法解而返回,那么让我们考虑接下来的两种策略,一是用更长的木棍来代替当前木棍,显然这样总长度会超过原始木棍的长度,违法。二是用更短的木棍组合来代替这根木棍,他们的总长恰好是当前木棍的长度,但是由于这些替代木棍在后面的搜索中无法得到合法解,当前木棍也不可能替代这些木棍组合出合法解。因为当前木棍的做的事这些替代木棍也能做到。所以,当出现加上某根木棍恰好能填满一根原始木棍,但又在后面的搜索中失败了,就不必考虑其他木棍了,直接退出当前的枚举。
- 考虑每根原始木棍的第一根木棍,如果当前枚举的木棍长度无法得出合法解,就不必考虑下一根木棍了,当前木棍一定是作为某根原始木棍的第一根木棍的,现在不行,以后也不可能得出合法解。也就是说每根原始木棍的第一根小木棍一定要成功,否则就返回。
DFS做题小结的更多相关文章
- 【做题】POJ3469 Dual Core CPU——第一道网络流
刚学了Dinic就开始做题,然后就崩了. 题意:若干个任务,可以放在两个CPU中任意一个上完成,各有一定代价.其中又有若干对任务,如果它们不在同一个CPU上完成,会产生额外代价.最小化并输出代价. 一 ...
- [日记&做题记录]-Noip2016提高组复赛 倒数十天
写这篇博客的时候有点激动 为了让自己不颓 还是写写日记 存存模板 Nov.8 2016 今天早上买了两个蛋挞 吃了一个 然后就做数论(前天晚上还是想放弃数论 但是昨天被数论虐了 woc noip模拟赛 ...
- SDOI2016 R1做题笔记
SDOI2016 R1做题笔记 经过很久很久的时间,shzr终于做完了SDOI2016一轮的题目. 其实没想到竟然是2016年的题目先做完,因为14年的六个题很早就做了四个了,但是后两个有点开不动.. ...
- poj1564 Sum It Up dfs水题
题目描述: Description Given a specified total t and a list of n integers, find all distinct sums using n ...
- AtCoder Grand Contest 11~17 做题小记
原文链接https://www.cnblogs.com/zhouzhendong/p/AtCoder-Grand-Contest-from-11-to-20.html UPD(2018-11-16): ...
- AtCoder Grand Contest 1~10 做题小记
原文链接https://www.cnblogs.com/zhouzhendong/p/AtCoder-Grand-Contest-from-1-to-10.html 考虑到博客内容较多,编辑不方便的情 ...
- 【做题】BZOJ2342 双倍回文——马拉车&并查集
题意:有一个长度为\(n\)的字符串,求它最长的子串\(s\)满足\(s\)是长度为4的倍数的回文串,且它的前半部分和后半部分都是回文串. \(n \leq 5 \times 10^5\) 首先,显然 ...
- 【做题】spoj4060 A game with probability——dp
赛前做题时忽然发现自己概率博弈类dp很弱,心好慌.(获胜概率或最优解期望) 于是就做了这道题,续了特别久. 一开始列dp式子的时候就花了很长时间,首先搞错了两次,然后忘记了根据上一轮dp值直接确定选什 ...
- noip做题记录+挑战一句话题解?
因为灵巧实在太弱辽不得不做点noip续下命QQAQQQ 2018 积木大赛/铺设道路 傻逼原题? 然后傻逼的我居然检查了半天是不是有陷阱最后花了差不多一个小时才做掉我做过的原题...真的傻逼了我:( ...
随机推荐
- SpringBoot非官方教程 | 第十四篇:在springboot中用redis实现消息队列
转载请标明出处: 原文首发于:https://www.fangzhipeng.com/springboot/2017/07/11/springboot14-redis-mq/ 本文出自方志朋的博客 这 ...
- linux下通过源码安装git
1.移除旧版本git [root@Git ~]# git --version ## 查看自带的版本git version 1.8.3.1 [root@Git ~]# yum remove git ## ...
- 设置全局导航栏颜色,标题大小和UIBarButtonItem字体大小
设置全局导航栏颜色,标题大小和UIBarButtonItem字体大小 在appdelegate里面设置 swift: UINavigationBar.appearance().barTintColor ...
- 修改pytorch官方实例适用于自己的二分类迁移学习项目
本demo从pytorch官方的迁移学习示例修改而来,增加了以下功能: 根据AUC来迭代最优参数: 五折交叉验证: 输出验证集错误分类图片: 输出分类报告并保存AUC结果图片. import os i ...
- python实现简单决策树(信息增益)——基于周志华的西瓜书数据
数据集如下: 色泽 根蒂 敲声 纹理 脐部 触感 好瓜 青绿 蜷缩 浊响 清晰 凹陷 硬滑 是 乌黑 蜷缩 沉闷 清晰 凹陷 硬滑 是 乌黑 蜷缩 浊响 清晰 凹陷 硬滑 是 青绿 蜷缩 沉闷 清晰 ...
- linux总结及常用命令
一.操作系统的作用: 1.是现代计算机系统中最基本和最重要的系统软件 2.承上启下的作用 3.向下对硬件操作进行封装 4.向上对用户和应用程序提供方便访问硬件的接口 二.不同领域的操作系统: 1 ...
- 源码安装CentOs7下的PHP7
首先安装APACHE环境,直接用yum安装 yum install httpd httpd-devel /etc/httpd/ systemctl start httpd.service #启动apa ...
- for循环小练习
for循环是前测试循环语句 for(初始值:判定条件:步长){ 循环语句 } For循环原理: For循环第一次执行:首先执行语句1,然后执行语句2,如果条件为真,向内执行执行循环语句3. 如果条件为 ...
- linux安装软件的几种方式(kali平台)和一些实用的软件(持续更新)
安装软件前我们先更改镜像源,编辑 /etc/apt/sources.list 文件, 在文件最前面添加以下条目: #中科大更新源 deb https://mirrors.ustc.edu.cn/kal ...
- html页面导出word文档
1.加入两个外部js 1)FileSaver.js /* FileSaver.js * A saveAs() FileSaver implementation. * 1.3.2 * 2016-06-1 ...