再附上一篇DFS详解的,不明白DFS原理的同学可以看一看:
https://blog.csdn.net/li_jeremy/article/details/83714298
以下是全网收集整理的和自己写的部分,绝对保证dfs轻松入门。
核心代码:
关于dfs参数问题,什么在变化,就把什么设置成参数。
void dfs()//参数用来表示状态
{
if(到达终点状态)
{
…//根据题意添加
return;
}
if(越界或者是不合法状态)
return;
if(特殊状态)//剪枝
return ;
for(扩展方式)
{
if(扩展方式所达到状态合法)
{
修改操作;//根据题意来添加
标记;
dfs();
(还原标记);
//是否还原标记根据题意
//如果加上(还原标记)就是 回溯法
}
}
}
为什么有的时候得要标记,因为这可以防止打转现象的发生,你可以想象如果没有标记,你在迷宫围绕着某条路径打转
但有的时候需要标记却不需要还原标记,为何不需要还原标记,不还原的意义是防止下次递归时走到已走过的位置,还原是可 以让下次递归可以在经过原先递归走过的路径,当然不可能和原先一模一样的路径
DFS全排列
#include<iostream>
using namespace std;
int n;
int book[10] = {0};
int a[10];
void dfs(int index)
{
if(index == n) //递归终止条件
{
for(int i = 0;i < n;++i)
cout << a[i];
cout << endl;
return ;
}
else
{
for(int i = 1;i <= n;++i)
{
if(!book[i])
{
book[i] = 1;//标记位
a[index] = i;//把符合的数字保存到数组中
dfs(index+1);
a[index] = 0;
book[i] = 0;//还原标记,以便回溯
}
}
}
}
void init()
{
for(int i = 0;i < 9;++i)
book[i] = 0;
}
int main()
{
while(cin >> n)
{
dfs(0);
init();
}
return 0;
}
方格填数
如下的10个格子
+–+–+–+
| | | |
+–+–+–+–+
| | | | |
+–+–+–+–+
| | | |
+–+–+–+
(如果显示有问题,也可以参看【图1.jpg】)
填入0~9的数字。要求:连续的两个数字不能相邻。
(左右、上下、对角都算相邻)
一共有多少种可能的填数方案?
请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
这就是道全排列的变式题(因为数字是0-9,且填入到格子中),因为它必须去检查所填的数字是否符合题目要求
思路:明显这是回溯题,可以用dfs做,当然你也可以套10个循环来做(TLE)
dfs的话这道题我们该如何进行递归,很明显这道题和全排列类似,所以我们可以一个方格一个方格来填空
由于题意要求连续的两个数字不能相邻
所以需要判断/
左,上,左上,右上
/,因为在你下面和右面的都还没填呢
#include<iostream>
#include<cmath>
#include<sstream>
#include<string>
#include<cstring>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
int ans = 0;
int dir[4][2] = {0,-1,-1,0,-1,-1,-1,1};
int book[10][10];
int a[10][10];
int vis[10];
/*左,上,左上,右上*/
bool check(int r,int c,int val)
{
for(int k = 0;k < 4;++k)
{
int tr = r + dir[k][0];
int tc = c + dir[k][1];
if(tr < 1 || tc < 1 || tr > 3 || tc > 4) continue;
if(abs(a[tr][tc]-val) <= 1) return false;
}
return true;
}
void dfs(int row,int col)
{
if(row == 3 && col == 4)
{
ans++;
return ;
}
if(col == 5)
{
row += 1;
col = 1;
}
for(int i = 0;i <= 9;++i)
{
if(!book[row][col] && !vis[i] && check(row,col,i))
{
book[row][col] = 1;
vis[i] = 1;
a[row][col] = i;
dfs(row,col+1);
book[row][col] = 0;
vis[i] = 0;
a[row][col] = -2;
}
}
}
int main()
{
for(int i = 0;i <= 5;++i)
{
for(int j = 0;j <= 9;++j)
a[i][j] = -2;
}
dfs(1,2);
printf("%d\n",ans);
return 0;
}
寒假作业
现在小学的数学题目也不是那么好玩的。
看看这个寒假作业:
□ + □ = □
□ – □ = □
□ × □ = □
□ ÷ □ = □
(如果显示不出来,可以参见【图1.jpg】)
每个方块代表1~13中的某一个数字,但不能重复。
比如:
6 + 7 = 13
9 – 8 = 1
3 * 4 = 12
10 / 2 = 5
以及:
7 + 6 = 13
9 – 8 = 1
3 * 4 = 12
10 / 2 = 5
就算两种解法。(加法,乘法交换律后算不同的方案)
你一共找到了多少种方案?
同样是全排列变式题,
思路:一个一个填,递归的参数应该是下标,用一维数组来存储,一种做法是,把加减乘除比较,不符合则返回上一层,还有一种做法,把12个位置先填满,在判断是否符合条件,这样做,时间复杂度更高
#include<iostream>
#include<cmath>
#include<sstream>
#include<string>
#include<cstring>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
int ans = 0;
int vis[10];
int a[13];
void dfs(int index)
{
if(index == 13)
{
//为何不能a[10]/a[11] && a[10] % a[11] == 0, 11/5 == 2吧
if(a[10] == a[11]*a[12])
{
ans++;
}
return ;
}
//+
if(index == 3)
{
if(!vis[a[1]+a[2]])
{
vis[i] = 1;
a[index] = i;
dfs(index+1);
vis[i] = 0;
a[index] = 0;
}
return ;
}
//-
if(index == 7)
{
if(a[4]-a[5] != a[6])
return ;
}
//*
if(index == 10)
{
if(a[7]*a[8] != a[9])
return ;
}
for(int i = 1;i <= 13;++i)
{
if(!vis[i])
{
vis[i] = 1;
a[index] = i;
dfs(index+1);
vis[i] = 0;
a[index] = 0;
}
}
}
int main()
{
dfs(1);
printf("%d\n",ans);
return 0;
}
剪邮票
如【图1.jpg】, 有12张连在一起的12生肖的邮票。
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连)
比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。
请你计算,一共有多少种不同的剪取方法。
请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
思路:也是一道全排列变式题,只不过这里我们只截取其中5个格子判断是否连通,把选中的五个棋子的位置保存起来,如何判断连通呢?回溯即可
/*#include<iostream>
#include<cmath>
#include<sstream>
#include<string>
#include<cstring>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
int ans = 0;
int vis[9][9];
int dir[4][2] = {0,1,1,0,0,-1,-1,0};
int book[10000][5][5];
bool check(int count)
{
if(count == 0) return true;
for(int k = 0;k < count;++k)
{
int flag = 1;
for(int i = 1;i <= 3;++i)
{
for(int j = 1;j <= 4;++j)
{
if(book[k][i][j] != vis[i][j]) flag = 0;
}
}
if(flag)
return false;
}
return true;
}
void add(int a[][9],int count)
{
for(int i = 1;i <= 3;++i)
{
for(int j = 1;j <= 4;++j)
{
book[count][i][j] = vis[i][j];
}
}
}
void dfs(int r,int c,int sum)
{
if(sum == 4)
{
if(check(ans))
{
add(vis,ans);
ans++;
for(int i = 1;i <= 3;++i)
{
for(int j = 1;j <= 4;++j)
{
printf("%d ",vis[i][j]);
}
printf("\n");
}
printf("----------------------------------------------\n");
}
return ;
}
for(int k = 0;k < 4;++k)
{
int tr = r + dir[k][0];
int tc = c + dir[k][1];
if(tr < 1 || tc < 1 || tr > 3 || tc > 4) continue;
if(!vis[tr][tc])
{
vis[tr][tc] = 1;
dfs(tr,tc,sum+1);
vis[tr][tc] = 0;
}
}
}
int main()
{
for(int i = 1;i <= 3;++i)
{
for(int j = 1;j <= 4;++j)
{
if(!vis[i][j])
{
vis[i][j] = 1;
dfs(i,j,0);
vis[i][j] = 0;
}
}
}
printf("%d\n",ans);
return 0;
}*/
//别人的做法
#include<iostream>
#include<cmath>
#include<sstream>
#include<string>
#include<cstring>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
int ans = 0;
int que[13][2]; //保存信息
int book[13][13];
int a[10];
int vis[14];
int dir[4][2] = {0,1,1,0,0,-1,-1,0};
int flag = 1;
//通过回溯来判断
int check(int x,int y)
{
if(book[x][y] == 0) return 0;
book[x][y] = 0;
return 1+check(x-1,y)+check(x+1,y)+check(x,y-1)+check(x,y+1);
}
void dfs(int sum,int last)
{
if(sum == 5)
{
memset(book,0,sizeof(book));
//这里如何去判重
for(int i = 0;i < 5;++i)
{
book[que[a[i]][0]][que[a[i]][1]]= 1;
}
flag = 1;
if(check(que[last][0],que[last][1]) == 5)
{
ans++;
}
return ;
}
for(int i = last+1;i <= 12;++i)
{
if(!vis[i])
{
vis[i] = 1;
a[sum] = i;
dfs(sum+1,i);
vis[i] = 0;
a[sum] = -1;
}
}
}
int main()
{
int n = 1;
for(int i = 1;i <= 3;++i)
{
for(int j = 1;j <= 4;++j)
{
//保存每个方格的横纵坐标
que[n][0] = i;
que[n++][1] = j;
}
}
dfs(0,0);
printf("%d\n",ans);
return 0;
}
初学dfs:先做最最最基础的题: http://codeup.cn/contest.php?cid=100000608。
做完了,你就入门了(绝对没有比这更基础的题了)
然后再做下面的题:
题型分类:
写过这些入门题后,我们可以将DFS题分为两大类:
1 . 地图型:这种题型将地图输入,要求完成一定的任务。因为地图的存在。使得题意清楚形象化,容易理清搜索思路。
POJ 1031棋盘问题
(类似八皇后问题)http://poj.org/problem?id=1321
AOJ 869-迷宫(遍历地图,四向搜索)
https://blog.csdn.net/chen_yuazzy/article/details/73656668
HDU 1035-Robot Motion(指定方向搜索,迷路(循环)判断)
http://acm.hdu.edu.cn/showproblem.php?pid=1035
HDU 1045-Fire Net(check函数,回溯)
http://acm.hdu.edu.cn/showproblem.php?pid=1045
HDU 1010-Tempter of the Bone(奇偶剪枝,回溯)
http://acm.hdu.edu.cn/showproblem.php?pid=1010
2 . 数据型:这种题型没有给定地图,一般是一串数字或字母,要求按照一定的任务解题。相对于地图型,这种题型较为抽象,需要在数据中进行搜索。数据以数组的形式存储,那么只要将数组也当作一张图来进行搜索就可以了。
HDU 1016-Prime Ring Problem(回溯、素数筛)
HDU 1258-Sum It Up(双重DFS递归,去重技巧)
HDU 1015-Safecraker(回溯,字符处理)
HDU 2676-Sudoku(抽象,回溯)
以下是一些DFS:
Sticks
http://acm.hdu.edu.cn/showproblem.php?pid=1455
http://acm.hdu.edu.cn/showproblem.php?pid=1455
http://acm.hdu.edu.cn/showproblem.php?pid=2553
http://acm.hdu.edu.cn/showproblem.php?pid=1426
http://acm.hdu.edu.cn/showproblem.php?pid=1528
http://acm.hdu.edu.cn/showproblem.php?pid=1175
http://acm.hdu.edu.cn/showproblem.php?pid=1010
http://acm.hdu.edu.cn/showproblem.php?pid=1312
http://acm.hdu.edu.cn/showproblem.php?pid=1716
http://acm.hdu.edu.cn/showproblem.php?pid=5547
http://acm.hdu.edu.cn/showproblem.php?pid=1181
http://acm.hdu.edu.cn/showproblem.php?pid=5305
记忆化搜索(DFS+DP):
http://acm.hdu.edu.cn/showproblem.php?pid=1978
http://acm.hdu.edu.cn/showproblem.php?pid=1078
http://acm.hdu.edu.cn/showproblem.php?pid=1428