DFS第一大类可以解决的问题
B F S / D F S ⇒ { F l o o d F i l l 模 型 图 与 树 的 遍 历 BFS/ DFS\Rightarrow \left\{\begin{matrix} Flood\ Fill 模型\\ 图与树的遍历 \end{matrix}\right. BFS/DFS⇒{
Flood Fill模型图与树的遍历
此类问题,一般是判断树与图的内部,是否连通
DFS第二类问题
搜索顺序,剪枝与优化,迭代加深, IDA*, 双向BFS。
这些问题基本是把整个问题看成是一个整体,问我们,能否从一个整体,转化为另外一个整体
把整体看成一个点,能否变化为另外一个点。在整体与整体之间做一个搜索。
BFS与DFS的比较
BFS:不仅可以知道A与B是否连通,同时还可以知道当前A与B的距离是否是最短的距离
DFS: 只能求出来A与B是否连通, 第1次搜出来A和B, 只能知道A,B是否连通,并不能通过此来判断A与B之间的距离最短
DFS(优点): 代码较短
AcWing 1112. 迷宫
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
char g[N][N];
int T;
int n;
bool st[N][N];
int dx[] = {
-1, 0, 1, 0}, dy[] = {
0, 1, 0, -1};
int xa, ya, xb, yb;
bool dfs(int x, int y){
if (g[x][y] == '#') return false; //一定要写在开头,因为起点可能是#
if (x == xb && y == yb) return true;
st[x][y] = true;
for (int i = 0; i < 4; i ++ ){
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= n) continue;
if (st[a][b]) continue;
if (dfs(a, b)) return true;
}
return false;
}
int main(){
cin >> T;
while (T -- ){
memset(st, 0, sizeof st);
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> g[i];
cin >> xa >> ya >> xb >> yb;
if (dfs(xa, ya)) puts("YES");
else puts("NO");
}
return 0;
}
回溯问题(恢复现场问题)
26:30
对于第一类问题,要保证每个点只被搜索1次,是不能恢复现场的
对于第二类问题,把整个棋盘看成一个点,是必须恢复现场的,一定要把棋盘恢复原状,才能搜索下一个分支
AcWing 1113. 红与黑
#include <iostream>
#include <cstring>
using namespace std;
const int N = 30;
char g[N][N];
int m, n;
bool st[N][N];
int dx[] = {
-1, 0, 1, 0}, dy[] = {
0, 1, 0, -1};
int dfs(int x, int y){
int cnt = 1;
st[x][y] = true;
for (int i = 0; i < 4; i ++ ){
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (st[a][b]) continue;
if (g[a][b] != '.') continue;
cnt += dfs(a, b);
}
return cnt;
}
int main(){
while (cin >> m >> n, m || n){
memset(st, 0, sizeof st);
for (int i = 0; i < n; i ++ ) cin >> g[i];
int x, y;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
if (g[i][j] == '@') x = i, y = j;
cout << dfs(x, y) << endl;
}
return 0;
}
AcWing 1116. 马走日
恢复现场写到dfs外面可以加快运行速度
#include <iostream>
using namespace std;
const int N = 15;
bool st[N][N];
int T, n, m, x, y;
int ans;
int dx[] = {
-2, -1, 1, 2, 2, 1, -1, -2}, dy[] = {
1, 2, 2, 1, -1, -2, -2, -1};
void dfs(int x, int y, int cnt){
if (cnt == n * m){
ans ++;
return ;
}
st[x][y] = true;
for (int i = 0; i < 8; i ++ ){
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (st[a][b]) continue;
dfs(a, b, cnt + 1);
}
st[x][y] = false;
}
int main(){
cin >> T;
while (T -- ){
ans = 0;
cin >> n >> m >> x >> y;
dfs(x, y, 1);
cout << ans << endl;
}
return 0;
}
AcWing 1117. 单词接龙
#include <iostream>
using namespace std;
const int N = 50;
int g[N][N]; //表示前一个单词与后一个单词最短重合距离,因为最短重合,才能使得龙最长
string word[N];
int n;
int used[N];
int ans;
void dfs(string dragon, int last){
ans = max((int) dragon.size(), ans);
used[last] ++;
for (int i = 0; i < n; i ++ )
if (g[last][i] && used[i] < 2)
dfs(dragon + word[i].substr(g[last][i]), i);
used[last] --;
}
int main(){
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> word[i];
char start;
cin >> start;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ ){
string a = word[i], b = word[j];
for (int k = 1; k < min(a.size(), b.size()); k ++ )
if (a.substr(a.size() - k, k) == b.substr(0, k)){
g[i][j] = k;
break;// 第1次搜到即为最短距离,因为长度是从小到大,直接break即可
}
}
for (int i = 0; i < n; i ++ )
if (word[i][0] == start)
dfs(word[i], i);
cout << ans << endl;
return 0;
}
AcWing 1118. 分成互质组
图中递归搜索树,表示的含义:
组1(1) : 组1中放了下标是1的数。
需要考虑的问题:
优化
方案优化(搜索空间减小)
如果当前的数可以加到最后一个组,是否有必要新开一个组。
即:在图中1方案可选的情况下,另外选择2方案是否合理。
证明:
如果下标1(可以放到组3)的数开了组4, 然后组4后续又加了一些数,去掉1,组4中的所有数仍然互质,因此下标为1的数,可以放到组3。
这样做的结果,可以使得搜索空间减小
顺序优化(组合数的方式去搜索)
因为下标为1,2,3的数,以1,2,3次序, 2, 3,1次序加入到组中,与加入的顺序无关。
因此我们按照组合的方式去搜索
按照下标从小到大的情况加入组中,不会重复搜索。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 11;
int n;
int p[N];
int group[N][N];
bool st[N];
int ans = N;
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
bool check(int group[], int gc, int i){
for (int j = 0; j < gc; j ++ )
if (gcd(p[group[j]], p[i]) > 1)
return false;
return true;
}
void dfs(int g, int gc, int tc, int start){
if (g >= ans) return ;
if (tc == n) ans = g;
bool flag = true;//从start开始找,是否有元素不能放到g组中
for (int i = start; i < n; i ++ )
if (!st[i] && check(group[g], gc, i)){
st[i] = true;
group[g][gc] = i; //group[g][gc] 用来记录当前g组gc位置放了原来数组中的哪个下标, 方便check函数
dfs(g, gc + 1, tc + 1, i + 1);
st[i] = false;
flag = false;
}
//新开一个分组
//由于dfs每层之间确定了顺序,所以期间是会有元素被漏掉的,【比如一开始你找的一串序列(1)是1,2,3,4 但是第二次(2)是1,3,4 很显然此时
//(2)还有一个3没有得到分组,需要从start=0开始再把它找出来! 因此这样做仿佛有点浪费时间呢!!】
//因此当所有元素都不能放进当前分组的时候 或者 当start=n-1了但是元素没有全部分组完毕时,要重新从start=0开始找,并且一定要有st数组!!!不然会把一个元素重复的分组!
if (flag) dfs(g + 1, 0, tc, 0);
}
int main(){
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> p[i];
dfs(1, 0, 0, 0); //1.搜索第1组, 2.从1组的0号位置开始搜 3.当前一共搜索了0个元素 4.从p[i]数组的第0个位置搜
cout << ans << endl;
return 0;
}