DFS(深度优先搜索)8种题型

👂 如果当时2020(不曾遗忘的符号) - 许嵩/朱婷婷 - 单曲 - 网易云音乐

👂 一个深爱的女孩 - 本兮 - 单曲 - 网易云音乐

半年前写了一半的博客.......(2023/7/14),今天花6小时给它补充完毕......

目录

🌼补充知识 

🌳分类 

🌼一, 1158: 八皇后

🌼二, 3472. 八皇后

🌼三,P1605 迷宫

🌼四,P1434 [SHOI2002] 滑雪

🌳AC  记忆化dfs

🌼五,指数型枚举

🌼六,排列型枚举

🌼七,组合型枚举

🌼八,油田 Oil Deposits

🌼九,P1019 [NOIP2000 提高组] 单词接龙

🍔总结


🌼补充知识 

精益求精

在开始之前,我想先对BFS和DFS作个简单的区分

dfs可以用递归来写,也可以用来写;bfs可以用数组模拟队列,也可以直接stl的队列

dfs很难找到最优解,只用来找到某个解,内存消耗较小;bfs则适用于找最优解,但不适用于深层次的搜索

dfs如果需要回溯,就需要取消标记;而bfs不需要取消标记

接下来谈谈最近dfs刷题的思考:

关于,为什么有些dfs需要在标记,递归后进行取消标记,而另一些题目则不需要取消标记呢?

eg1: 给定起点,走5步,求所有可能到达的点

eg2: 给定起点,不限制步数,求能否到达终点

题1限制了步数,所以经过某点的路径会影响结果,那么就需要多次经过同一个点(意味着需要回溯),所以需要取消标记

问题2经过某点的路径不影响结果(不限制步数),所以不需要多次经过同一个点(不需要回溯),所以不需要取消标记

一次能把所有点走完,不需要取消标记(不需要回溯),走过的点就不会再走了

而如果部分路径需要多次访问,这时就需要回溯取消标记了,不然就没法访问了

-- -- End~

知识点 

下面,先结合百度,CSDN,博客园,知乎,《啊哈算法》,oi-wiki,增加对dfs的了解

(1条消息) 《啊哈算法》第四章之深度优先搜索_码龄?天的博客-CSDN博客

DFS(搜索) - OI Wiki (oi-wiki.org)

深度优先搜索 - AcWing

🌳分类 

1,n皇后

基于国际象棋规则,皇后可以攻击(1)同一行(2)同一列,

(3)同一斜线(包括主对角线和副对角线的方向)  的其他单位

在n * n的棋盘中,放置n个皇后,使它们互相之间不攻击 

2,迷宫

迷宫里有几个点:

1,递归是很多人难以理解的,比如return;

2,模板则是,遍历 + 判断目标,其中遍历里的回溯 = 标记 + 递归 + 取消标记

3,走迷宫需要建立一个二维数组保存地图,并且需要一个二维方向数组

《啊哈算法》第四章之深度优先搜索_码龄?天的博客-CSDN博客

3,油田

有点类似记忆化dfs的过程,但是完全不一样。

记忆化dfs可以用一个数组保存过程,比如滑雪那题,找最长路程,后续遍历到以前走过的点,就不需要再遍历了,类似剪枝。

而油田,对每个点dfs后,将访问过的点在地图上直接操作(标记),省去了 vis[][] 标记数组,节省了空间。

4,单词接龙

5,记忆化dfs

6,dfs枚举

对dfs枚举做个总结,

1,都需要一个数组a[]来保存最后输出的答案

2,排列型枚举,就是全排列,dfs模板,可以画递归树辅助理解

3,组合型枚举,1~n中取m个数,由于是组合,打算顺序视作相同,如果处理呢?只需要保证后一个数 > 前一个数,加个 if 判断(还可以剪枝)

4,指数型枚举,和排列型稍作区别,排列型需要对每一个数进行选择,而指数型,只需要判断选 / 不选,不需要for(... i ~ n ...)

(1)指数型枚举

1~n中随机选择任意多个,输出所有方案的选择

int vis[N];
void dfs(int x)
{//到达边界if(x == n) { ...return;}//只有选 / 不选两种可能//选vis[x] = 1; //标记选择dfs(x + 1); //递归vis[x] = 0; //取消标记//不选dfs(x + 1); //vis[x] = 0; 默认未选择
}

(2)组合型枚举

(3)排列型枚举(全排列)

🌼一, 1158: 八皇后

P1158 - 八皇后 - New Online Judge (ecustacm.cn)

标签:基础题,USACO,深度优先搜索 

 下面展示两种做法

1,利用b, c, d三个数组,分别标记在某一列,某一右斜,某一左斜是否冲突

2,然后dfs(int row)的参数就是行数,避免了对第几行是否可行的讨论

3,在遍历的过程中递归

4,不是标准的dfs模板,但是做法值得学习,而且复杂度比直接套模板低

row就是行,j 就是列,row - j 就是主对角线,row + j 就是副对角线(row - j + 13防止负数

主对角线,row - j 相等;副对角线,row + j 相等 

AC  代码

#include<iostream>
int a[100], b[100], c[100], d[100];
int ans = 0, n;
using namespace std;
void dfs(int row)
{for(int j = 1; j <= n; ++j)if(b[j] == 0 && c[row - j + 13] == 0 && d[row + j] == 0) {a[row] = j;//标记b[j] = 1; //列已放置c[row - j + 13] = 1; //主对角线d[row + j] = 1; //副对角线if(row == n) { //得到一个答案ans++;if(ans <= 3) {for(int k = 1; k <= n; ++k)cout<<a[k]<<" ";cout<<endl;}}else dfs(row + 1); //递归//取消标记b[j] = 0;c[row - j + 13] = 0;d[row + j] = 0;}
}
int main()
{cin>>n;dfs(1);cout<<ans;return 0;
}
/*
a[row] = j放在第row行第j列
b[j] = 1第j列已放置
c[row - j + 13]主对角线已放置, +13防止下标为负
d[row + j]副对角线已防放置
*/

AC  代码2

标准的dfs模板

代码2耗时555ms,代码1耗时355ms

关于abs的解释

实际上还是对行,列,主,副对角线的判断,不过这次主副对角线一行搞定

dfs中for循环里,要放置的是第step + 1行, 第 j 列。前面遍历的是第 i 行第 a[i] 列
行差值(绝对值) == 列差值   表示主副对角线有重合,就break

代码第 19 行

for(int i = 1; i <= step; ++i) //第 i 行

1) 遍历到 step 是因为,后面的行还没放置到

2) i <= step 是因为,主函数中 step 从 0 开始,此时 1 ~ step 表示,遍历到当前行的

前一行,如果 step 从 1 开始,只能 i < step,否则就会 WA

#include<iostream>
using namespace std;
int a[100], vis[100], n, ans; //vis[j]统计第 j 列是否被放置void dfs(int step) //已放置step个皇后, 第 step + 1行
{if(step == n) {ans++;if(ans <= 3) {for(int k = 1; k <= n; ++k)cout<<a[k]<<" ";cout<<endl;}return;}for(int j = 1; j <= n; ++j) { //第 j 列if(!vis[j]) { //第 j 列未放置int flag = 1; for(int i = 1; i <= step; ++i) //第 i 行if(abs(step + 1 - i) == abs(j - a[i])) {flag = 0;break;}if(flag) { //可以放置a[step + 1] = j;vis[j] = 1; //标记dfs(step + 1); //递归vis[j] = 0; //取消标记}}}
}int main()
{cin>>n;dfs(0); //第1行开始cout<<ans;return 0;
}

🌼二, 3472. 八皇后

3472. 八皇后 - AcWing题库

标签:中等,DFS,北京大学考研机试题 

 在第一题的基础上,将结果存起来,不用排序!!!

主要目的是练手,再敲一遍,增加熟练度

卡点 

1,在dfs函数中,就可以用 *= 10的方法操作

2,最好不要用string b[]这样保存,否则还得加数字转化为字符串再 +=

AC  代码

#include<iostream>
#include<cmath>
using namespace std;// 第 i 行第 a[i] 列, b[]每一组答案, vis[] 某一列已放置
int a[10], b[100], vis[10], cnt = 1;void dfs(int r)
{if (r == 9) { // 放置完毕for (int i = 1; i <= 8; ++i) {b[cnt] *= 10;b[cnt] += a[i];}cnt++;return; // 回溯}// 遍历// 当前(r, c)  遍历点(i, a[i])for (int c = 1; c <= 8; ++c) { // 第 c 列int flag = 1;if (vis[c]) continue;for (int i = 1; i < r; ++i) { // 遍历 1 ~ r-1 行if (abs(r - i) == abs(c - a[i])) { // 左斜 / 右斜 已放置flag = 0;break;}}if (flag) {a[r] = c; // 更新vis[c] = 1; // 标记dfs(r + 1); // 递归vis[c] = 0; // 取消标记}}
}int main()
{int T, n;dfs(1); // 第一行开始cin >> T;while (T--) {cin >> n;cout << b[n] << endl; // 第 n 种答案}return 0;
}
6
1
15863724
2
16837425
19
36271485
92
84136275
88
75316824
66
62713584

🌼三,P1605 迷宫

P1605 迷宫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

标签:DFS,USACO,枚举,普及-

 

记得标记起点已走过..............时隔一年再次做这道题,还是错在了这里....2024/2/2

强烈建议不会dfs的看看这个博客,里面的迷宫题模板,直接套在本题即可

(1条消息) 《啊哈算法》第四章之深度优先搜索_码龄?天的博客-CSDN博客

通过二维数组a[10][10]保存迷宫地图,障碍就a[i][j] = 1; 已走过的点就a[i][j] = -1,开始先初始化为a[i][j] = 0,表示都未走过 

AC  代码

如果next数组声明为全局变量,会报错 the reference to next is ambiguous,因为next是C++标准库中一个函数,会产生命名冲突,所以,声明全局的话,next[][]改成dir[][]即可 

#include<iostream>
using namespace std;
int a[10][10], ans = 0;
int sx, sy, fx, fy, n, m;
void dfs(int x, int y)
{//到达目标if(x == fx && y == fy) {ans += 1;return; //回溯}int next[4][2] = { //方向数组, 循环得到下一步坐标{-1, 0}, //上{1, 0},  //下{0, -1}, //左{0, 1}}; //右//枚举四种走法int tx, ty; //临时变量for(int i = 0; i < 4; ++i) {tx = x + next[i][0];ty = y + next[i][1];//判断越界if(tx < 1 || ty < 1 || tx > n || ty > m)continue; //换个方向//不是陷阱且未走过if(a[tx][ty] == 0) {a[tx][ty] = -1; //标记走过dfs(tx, ty);   //递归a[tx][ty] = 0; //取消标记}}
}
int main()
{int t;cin>>n>>m>>t;cin>>sx>>sy>>fx>>fy;a[sx][sy] = -1; //标记初始走过int h, y;for(int i = 0; i < t; ++i) {cin>>h>>y;a[h][y] = 1; //1为障碍}dfs(sx, sy);cout<<ans;return 0;
}

🌼四,P1434 [SHOI2002] 滑雪

P1434 [SHOI2002] 滑雪 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

标签:搜索,递归,记忆化搜索,普及/提高-

一样的题,同样的代码能AC

这是一道20年前的省选题

这里采用记忆化dfs,听起来,记忆化,很高大上,实际上本题只多了2行代码。。。。。

总结就是dfs模板 + 2行代码 

思路

第一次没用记忆化写了个void dfs,只AC了30%....

int dfs(int x, int y),然后声明全局二维数组Max[110][110]表示从该点出发的最大距离

由样例可知,初始点也算1距离,所以最后ans + 1

至于记忆化的2行呢,请看下面代码

int Max[110][110];
int dfs(int x, int y)
{if(Max[x][y]) return Max[x][y];
}

return Max[x][y],避免了重复计算,是不是有点像dp(动态规划)

题解👇

P1434 [SHOI2002] 滑雪 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

🌳AC  记忆化dfs

当然卡了好一会,看了别人题解才AC

解释下第20行,Max[x][y]表示(x, y)点出发的最大距离,Max[tx][ty]表示(tx, ty)出发的最大距离

由于(x, y) --> (tx, ty),所以后一个点距离+1,就等于前一个点可能的最大距离(利用前面已经保存的结果,就不用重新算一次已经算过的过程了)

#include<iostream>
using namespace std;
int Max[110][110], e[110][110]; //Max数组保存该点滑行距离
int n, m;
int nex[4][2] = {{1,0},{-1,0},{0,1},{0,-1}}; //上下左右
//记忆化dfs类似DP
int dfs(int x, int y)
{//关键: 记忆化dfsif(Max[x][y]) return Max[x][y]; //已经得到的数据,就不用再算了int tx, ty;//遍历4个方向for(int i = 0; i < 4; ++i) {tx = x + nex[i][0];ty = y + nex[i][1];if(tx < 0 || ty < 0 || tx >= n || ty >= m)continue;if(e[x][y] > e[tx][ty]) {dfs(tx, ty);Max[x][y] = max(Max[x][y], Max[tx][ty] + 1); //这行也很关键}}return Max[x][y];
}
int main()
{cin>>n>>m;//读入地图for(int i = 0; i < n; ++i)for(int j = 0; j < m; ++j)cin>>e[i][j];//dfs遍历for(int i = 0; i < n; ++i)for(int j = 0; j < m; ++j)Max[i][j] = dfs(i, j);//遍历Max数组得到最大长度int num = 0;for(int i = 0; i < n; ++i)for(int j = 0; j < m; ++j)num = max(num, Max[i][j]);cout<<num + 1; //依题意加上初始点return 0;
}

洛谷上,不用记忆化,直接dfs能AC  90%

因为不熟练,我又在蓝桥杯官网敲了一次

AC  代码2

滑行 - 蓝桥云课 (lanqiao.cn)

#include<iostream>
using namespace std;
int Max[110][110], e[110][110];
int n, m, ans, nex[4][2] = {{1, 0},{-1,0},{0,1},{0,-1}};
int dfs(int x, int y)
{if(Max[x][y]) return Max[x][y];int tx, ty;for(int i = 0; i < 4; ++i) {tx = x + nex[i][0];ty = y + nex[i][1];if(tx < 0 || ty < 0 || tx >= n || ty >= m) continue;if(e[x][y] > e[tx][ty]) {Max[x][y] = max(Max[x][y], dfs(tx, ty) + 1);}}return Max[x][y];
}
int main()
{cin>>n>>m;for(int i = 0; i < n; ++i)for(int j = 0; j < m; ++j)cin>>e[i][j];for(int i = 0; i < n; ++i)for(int j = 0; j < m; ++j)  Max[i][j] = dfs(i, j);for(int i = 0; i < n; ++i)for(int j = 0; j < m; ++j)ans = max(ans, Max[i][j]);cout<<ans + 1;return 0;
}

🌼五,指数型枚举

92. 递归实现指数型枚举 - AcWing题库

标签:递归,简单 

1~n个数,随机选取任意多个,那么每个数只有选和不选2种可能

所以方案数 = 2^n,单个方案n个数,那么时间复杂度 = O(2^15 * 15) ≈ 7.5 * 10^5

暴力没问题

因为输出数据达10^5以上,考虑printf(),提高速度

AC  代码1

1年后二刷,漏了空格,卡了半小时....2024/2/2

#include<iostream>
#include<cstdio> //printf()
using namespace std;
const int N = 20;int n, vis[N]; //vis表示该位置是否选了
void dfs(int x) //x表示当前位置
{if(x == n) { //到达边界for(int i = 0; i < n; ++i) //从前往后遍历每一个if(vis[i] == 1) //已选中printf("%d ", i + 1);printf("\n");return; //不要忘记return}//选vis[x] = 1; //标记选它dfs(x + 1); //递归vis[x] = 0; //取消标记//不选dfs(x + 1); //不选默认vis[x] == 0, 不需要处理
}int main()
{cin>>n;dfs(0);return 0;
}
4
1 2 3 4
1 2 3
1 2 4
1 2
1 3 4
1 3
1 4
1
2 3 4
2 3
2 4
2
3 4
3
4

补充知识点

1,

2的多少次方

“<<”是按位左移运算符,它会将某个二进制数向左移动指定的位数。例如,1 << 15表示将二进制数1向左移动15位,即:

0000000000000001 << 15 = 1000000000000000

所以,1 << 15在C++中表示的是二进制数1000000000000000

这个表达式可以用来快速计算2^15的值

具体来说,对于任意一个非负整数n,2^n可以表示为1左移n位,即1 << n 

2,

puts("")相当于回车,puts相当于输出一个字符串 + 回车,当字符串为空,等价于只输出回车

AC  代码2

我们也可以把两个printf删掉,将所有方案用一个二维数组ways统计

#include<iostream>
#include<cstdio> //printf()
using namespace std;
const int N = 20;int ways[1 << 15][16], cnt; //第cnt种方案
int n, vis[N]; //vis表示该位置是否选了
void dfs(int x) //x表示当前位置
{if(x == n) { //到达边界for(int i = 0; i < n; ++i) //从前往后遍历每一个if(vis[i] == 1) //已选中ways[cnt][i] = i + 1; //下标0开始,要加1cnt++; //方案书 +1return; //不要忘记return}//选vis[x] = 1; //标记选它dfs(x + 1); //递归vis[x] = 0; //取消标记//不选dfs(x + 1); //不选默认vis[x] == 0, 不需要处理
}int main()
{cin>>n;dfs(0);//输出方案数for(int i = 0; i < cnt; ++i) {for(int j = 0; j < n; ++j) {if(ways[i][j]) printf("%d ", ways[i][j]);}puts(""); //换行}return 0;
}

🌼六,排列型枚举

复杂度O(n * n!)

94. 递归实现排列型枚举 - AcWing题库

标签:简单,递归

递归树👇辅助理解

我们采取枚举位置的方法,枚举位置x,再用for循环判断放第i个数

AC  代码

#include<iostream>
#include<cstdio> //printf()
using namespace std;
const int N = 10;int n, vis[10], a[N];
void dfs(int x)
{if(x == n + 1) { //边界for(int i = 1; i <= n; ++i)printf("%d ", a[i]);puts(""); //换行}for(int i = 1; i <= n; ++i)if(vis[i] == 0) { //数字i未选择vis[i] = 1; //标记i已选择a[x] = i; //数字i放入第x个坑dfs(x + 1); //递归下一个位置//恢复现场//a[x] = 0; //这步不需要, 每一次开始前都会被覆盖vis[i] = 0; //取消数字i的标记}
}int main()
{cin>>n;dfs(1); //第1个位置开始return 0;
}
4
1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
2 1 4 3
2 3 1 4
2 3 4 1
2 4 1 3
2 4 3 1
3 1 2 4
3 1 4 2
3 2 1 4
3 2 4 1
3 4 1 2
3 4 2 1
4 1 2 3
4 1 3 2
4 2 1 3
4 2 3 1
4 3 1 2
4 3 2 1

🌼七,组合型枚举

93. 递归实现组合型枚举 - AcWing题库

组合型,只是比排列型,多了个限制条件,由于不能重复,也就是123 231 132等等默认是一种情况,我们可以要求后一个比前一个大

只需在for循环的 if 中加个判断

注意!代码第15 ~16行 if 的剪枝,没有也可以,但是会慢很多(100ms和1162ms的区别),剪枝,意思是不符合条件时终止后面的运算(具体时间差多少请看👇)

AC  代码

#include<iostream>
using namespace std;
const int maxn = 26;int n, m, a[26];void dfs(int step) //已选step个数字
{if(step == m) {for(int i = 0; i < m; ++i)cout<<a[i]<<" ";cout<<endl;}for(int i = 1; i <= n; ++i) {if(n - i + 1 < m - step) //还需要选 m-step 个数字, 还能选 n-i+1 个数字break; //剪枝, 表示剩余数字不够选了if(!step || a[step - 1] < i) { //第一个数字默认可选; 上一个数字小于当前的 ia[step] = i;dfs(step + 1);}}
}int main()
{cin>>n>>m;dfs(0);return 0;
}

🌼八,油田 Oil Deposits

标签:普及/提高-,DFS,连通块

油田 Oil Deposits - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

void dfs()中,递归dfs(tx, ty);前,要加个判断 if(a[tx][ty] == '@'),否则,不是油田是不能走的,疏忽了

AC  代码1

直接修改 字符矩阵,a[][] = '*' 标记访问过

#include<iostream>
#include<cstring> //memset()
using namespace std;
int n, m;
char a[110][110];void dfs(int x, int y)
{a[x][y] = '*'; //标记int dir[8][2] = {{1,0},{-1,0},{0,1},{0,-1},{-1,1},{-1,-1},{1,1},{1,-1}}; //8个方向int tx, ty;for(int i = 0; i < 8; ++i) {tx = x + dir[i][0], ty = y + dir[i][1];if(tx < 0 || ty < 0 || tx >= n || ty >= m)continue;if(a[tx][ty] == '@') //只有油田才能走dfs(tx, ty);//连通块不需要取消标记}
}int main()
{while(cin>>n>>m) {if(!n && !m) break;int ans = 0;for(int i = 0; i < n; ++i)for(int j = 0; j < m; ++j)cin>>a[i][j]; //读入油田for(int i = 0; i < n; ++i)for(int j = 0; j < m; ++j)if(a[i][j] == '@') { //从一个点出发ans++;dfs(i, j); //遍历 -- 所有能到达的'@'标记为'*'}cout<<ans<<endl;}return 0;
}

AC  代码2

vis[][] = 1 标记走过

#include<iostream>
#include<cstring>
using namespace std;int n, m, ans;
int vis[110][110];
char e[110][110]; // 存储地图void dfs(int x, int y)
{vis[x][y] = 1; // 标记// 8 个方向int dir[8][2] = {{1,0},{-1,0},{0,1},{0,-1},{-1,1},{-1,-1},{1,1},{1,-1}};for (int i = 0; i < 8; ++i) { // 遍历 4 个方向// 新的坐标int tx = x + dir[i][0], ty = y + dir[i][1];if (tx < 0 || ty < 0 || tx >= n || ty >= m) continue; // 越界if (e[tx][ty] == '@' && !vis[tx][ty]) dfs(tx, ty); // 递归}
}int main()
{while (cin >> n >> m) {if (!n && !m) break;ans = 0;memset(vis, 0, sizeof(vis));for (int i = 0; i < n; ++i)for (int j = 0; j < m; ++j)cin >> e[i][j]; // 读入for (int i = 0; i < n; ++i)for (int j = 0; j < m; ++j)if (e[i][j] == '@' && !vis[i][j]) {dfs(i, j); // 对每一个未标记的油田dfsans++; // 每次dfs,会遍历一大片(8个方向邻接的所有油田)}cout << ans << endl;} return 0;
}

🌼九,P1019 [NOIP2000 提高组] 单词接龙

标签:字符串,搜索,普及/提高-

P1019 [NOIP2000 提高组] 单词接龙 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

一道上古时代的题....(要注意的点挺多的,都在注释里)

解释1

具体解释下,main函数中,初始dfs()的传参👇

dfs(" "+s[n], 1);

一开始,关于如何处理,用特定字符开头,挺苦恼的,所以采取这种方式传参

用样例举例

" "空格  +  字符串"a",当遇到 at,考虑到Link()函数中的dfs()递归👇

dfs(s[i], len_now + s[i].size() - k); //递归

递归后的长度(即a 和 at 拼凑后的长度),就是 1 + 2 - 1 == 2,也就实现了给定字符开头

避免了开头的 a 因为被包含而被处理掉

解释2

题目原文“另外相邻的两部分不能存在包含关系,例如 at 和 atide 间不能相连。”

这里其实有歧义,不是不能包含,而是包含后长度必须增加,这里只可意会

AC  代码

#include<iostream>
using namespace std;string s[30];
int use[30], ans, n; //use[]统计使用次数int Link(string s1, string s2) //返回s1, s2最小重叠长度
{int Min_len = min(s1.size(), s2.size());for(int i = 1; i < Min_len; ++i) {//i 重叠长度int flag = 1;for(int j = 0; j < i; ++j)  //j s2中下标if(s1[s1.size() - i + j] != s2[j]) { //不匹配flag = 0;break;}if(flag) return i;}return 0; //最小重叠长度为0
}void dfs(string now, int len_now) //len_now 拼凑后的长度
{ans = max(ans, len_now); //更新答案for(int i = 0; i < n; ++i) {if(use[i] >= 2) continue; //已选2次, 不能再选了int k = Link(now, s[i]); //后接s[i]的重叠长度if(k) { //满足递归条件use[i]++; //标记dfs(s[i], len_now + s[i].size() - k); //递归use[i]--; //取消标记}}
}int main()
{cin>>n;for(int i = 0; i <= n; ++i)cin>>s[i]; //读入字符串, s[n]为起始字符串dfs(" "+s[n], 1);cout<<ans;return 0;
}

AC  代码2

时隔1年,回来二刷写的详解版本,对照着看

#include<iostream>
#include<cmath> // min()
using namespace std;int ans, vis[30], n; // vis[] 记录每个单词只能用 2 次
string s[30];// 返回 s1--s2 (最小)重叠长度
int Link(string s1, string s2) 
{int Min = min(s1.size(), s2.size()); // < Min 避免包含for (int i = 1; i < Min; ++i) { // i 重叠长度int flag = 1;for (int j = 0; j < i; ++j) // j 字符串 s2 下标// s1 从末尾开始,和 s2 开头对比if (s1[s1.size() - i + j] != s2[j]) {flag = 0; // 当前不匹配了break;}if (flag)  // 当前长度 i 匹配return i;}return 0; // 重叠长度为 0
}// now 尾巴字符串   both_len 拼凑后长度
void dfs(string now, int both_len) 
{ans = max(ans, both_len); // 更新最大长度// 遍历for (int i = 0; i < n; ++i) { // 新的字符串 s[i]if (vis[i] >= 2) continue; // 最多使用 2 次int k = Link(now, s[i]); // 后接 s[i] 的重叠长度if (k) { // 存在重叠, 接龙后长度 upvis[i]++; // 标记dfs(s[i], both_len + s[i].size() - k); // 递归vis[i]--; // 取消标记}}
}int main()
{cin >> n;for (int i = 0; i <= n; ++i)cin >> s[i]; // s[n] 即 龙头字母// " " 防止龙头字母误判包含; 1 表示龙头字母长度dfs(" " + s[n], 1); cout << ans;return 0;
}

🍔总结

(一)n皇后,横 + 纵 + 主副对角线,用数组标记或找规律break
(二)迷宫,方向数组 + dfs模板,可在二维数组直接标记,注意传参(标记起点走过
(三)滑雪,记忆化 + dfs模板,记忆化通过Max[][]避免重复搜索
(四)指数型枚举,1~n取任意个数,只有选 / 不选2种,将dfs模板for循环的遍历变成对2种情况的选择
(五)排列型枚举,全排列,dfs模板,画递归树辅助理解
(六)组合型枚举,1~n取m个数,dfs模板 + 限制条件(新取的数字要比上一个大),可剪枝(未选个数 < 待选个数 -- break)
(七)油田,连通块dfs,8个方向,对每个点dfs,将走过的点标记

(连通块,对每一个 dfs() 一次,所以不需要取消标记)只需在 dfs() 开头 标记
(八)单词接龙,(1)初始传参,dfs(" "+"...", 1)避免龙头字母被包含(2)Link()返回最小重叠长度(3)dfs() 遍历所有字符串

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/541849.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sqllab第十七关通关笔记

知识点&#xff1a; 错误注入update更新语句 update 表名 set 字段‘输入’ where 字段名数据 通过admin admin进行输入发现是一个数据更新的语句 推测原始语句为update 表 set password‘’ where username 直接构造payload:passwdadminandexp(710) 发现有错误提示&#xf…

了解常用开发模型 -- 瀑布模型、螺旋模型、增量与迭代、敏捷开发

目录 瀑布模型 开发流程 开发特征 优缺点 适用场景 螺旋模型 开发流程 开发特征 优缺点 适用场景 增量与迭代开发 什么是增量开发&#xff1f;什么是迭代开发&#xff1f; 敏捷开发 什么是敏捷开发四原则&#xff08;敏捷宣言&#xff09;&#xff1f; 什么是 s…

微服务技术栈SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式(五):分布式搜索 ES-中

文章目录 一、DSL查询文档1.1 简单查询1.2 复合查询 二、搜索结果处理三、RestClient演示 查询与结果分析四、案例4.1 问题解析4.2 代码4.2.1 实体bean4.2.2 控制层4.2.3 业务service4.2.4 启动类 一、DSL查询文档 1.1 简单查询 # 1. DSL查询 # 1.1 查询所有GET /hotel/_searc…

【解读】NIST网络安全框架CSF 2.0

2014年&#xff0c;NIST&#xff08;美国国家标准与技术研究所&#xff0c;类似于中国的工信部&#xff09;首次发布了网络安全框架CSF&#xff08;Cybersecurity Framework)&#xff0c;十年后&#xff0c;在2024年2月26日发布了重大更新&#xff08;CSF 2.0&#xff09;&…

百度paddleocr GPU版部署

显卡&#xff1a;NVIDIA GeForce RTX 4070&#xff0c;Nvidia驱动程序版本&#xff1a;537.13 Nvidia驱动程序能支持的最高cuda版本&#xff1a;12.2.138 Python&#xff1a;python3.10.11。试过python3.12&#xff0c;安装paddleocr失败&#xff0c;找不到相关模块。 飞桨版本…

爬虫 某物流

目标地址 url "https://api.jdl.com/aging/feeInquiryNewByJDL" 加密参数 ciphertext和data 搜关键字ciphertext跟着栈走 很明显的DES加密 window globalconst e require(jsencrypt); // const e require(JSEncrypt) // e r(775).JSEncrypt // const t requi…

Java数据结构-优先级队列

文章目录 前言一、优先级队列1.1 概念 二、优先级队列的模拟实现2.1 堆的概念2.2 堆的存储方式2.3 堆的创建2.3.1 堆向下调整2.3.2 堆的创建2.3.3 建堆的时间复杂度 2.4 堆的插入与删除2.4.1 堆的插入2.4.2 堆的删除 2.5 用堆模拟实现优先级队列 三、常用接口介绍3.1 PriorityQ…

swiftUI中的可变属性和封装

swiftUI的可变属性 关于swift中的属性&#xff0c;声明常量使用let &#xff0c; 声明变量使用var 如果需要在swiftUI中更改视图变化那么就需要在 var前面加上state 。 通过挂载到state列表 &#xff0c;从而让xcode找到对应的改变的值 例子&#xff1a; import SwiftUIstruc…

使用 ZipArchiveInputStream 读取压缩包内文件总数

读取压缩包内文件总数 简介 ZipArchiveInputStream 是 Apache Commons Compress 库中的一个类&#xff0c;用于读取 ZIP 格式的压缩文件。在处理 ZIP 文件时&#xff0c;编码格式是一个重要的问题&#xff0c;因为它决定了如何解释文件中的字符数据。通常情况下&#xff0c;Z…

Spring boot java: 无效的目标发行版: 18

idea 搭建spring boot 报错java: 无效的目标发行版: 18 本人jdk 1.8 解决方案如下&#xff1a;

Vue2+ElementUI表单、Form组件的封装

Vue2ElementUI表单、Form组件的封装 &#xff1a;引言 在 Vue2 项目中&#xff0c;ElementUI 的 el-form 组件是常用的表单组件。它提供了丰富的功能和样式&#xff0c;可以满足各种需求。但是&#xff0c;在实际开发中&#xff0c;我们经常会遇到一些重复性的需求&#xff0c…

Prometheus 轻量化部署和使用

文章目录 说明Prometheus简介Grafana简介prometheus和Grafana的关系环境准备&#xff08;docker&#xff09;docker安装时间时区问题&#xff08;我的代码中&#xff09;dockers镜像加速和服务器时区设置 数据库准备(mysql、redis)mysql配置redis配置 Prometheus、grafana下载和…