int k, n; int xa, ya, xb, yb; char g[N][N]; bool st[N][N];
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
booldfs(int x, int y){ // 递归中,首先写出边界条件的判断 if (g[x][y] == '#') returnfalse; // 即使是终点和起点,如果为'#',也是不连通 // 注意判断可以放在递归后最开始时,也可以放在递归前。要结合题目初始和最终条件进行选择 // 但不要两处都进行判断 if (x == xb && y == yb) returntrue; st[x][y] = true; // 不同于bfs,不要在递归后再写一遍,会重复 // g[x][y] = '#'; // 直接通过改变状态进行标记
// 枚举所有可移动方向 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)) returntrue; // 递归时如何合理编写返回值、传递要求解的值是一个小难点 // 这里不能直接return dfs(a, b) // 因为当dfs(a, b)为false时,还要继续枚举其他方向 // 递归后一定要判断是否需要回溯和恢复现场 // 这里不需要恢复现场,属于内部搜索,拓展时不会改变当前状态 } returnfalse; }
intmain(){ scanf("%d", &k); while (k --){ scanf("%d", &n); for (int i = 0; i < n; ++ i) scanf("%s", g[i]); scanf("%d%d%d%d", &xa, &ya, &xb, &yb); memset(st, 0, sizeof st); // 不同于bfs,dfs一定要定义形参,而不是使用全局变量,方便递归 if (dfs(xa, ya)) puts("YES"); elseputs("NO"); } return0; }
# DFS之连通性模型——AcWing 1112. 迷宫 from sys import setrecursionlimit defmain(): T = int(input()) setrecursionlimit(5000) # Python中系统栈默认的递归深度限制为1000, # 一般最大可修改到10^5,再大的话可能就需要手写栈等策略 defdfs(x, y): if x < 0or x >= n or y < 0or y >= n or arr[x][y] == '#'or vis[x][y]: returnFalse if (x, y) == (ex, ey): returnTrue vis[x][y] = True# 更新判重 return dfs(x - 1, y) or dfs(x + 1, y) or dfs(x, y - 1) or dfs(x, y + 1) # 遍历子节点,这里不需要回溯恢复现场所以可以直接这么xie for _ inrange(T): n = int(input()) arr = [input() for _ inrange(n)] vis = [[False] * n for _ inrange(n)] sx, sy, ex, ey = map(int, input().split()) print("YES"if dfs(sx, sy) else"NO") if __name__ == "__main__": main()
intdfs(int x, int y){ int cnt = 1; // 只算当前点的话个数为1,不是0 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; // 注意dfs中一个注意点是如何在递归中统计要求的量 // 这里的思路是只统计以当前点为起点的数量,并进行前传,而不用把前面的个数传到后面 // 这是由于这里求的是所有方案数,前面的点有多种选择,不方便后传(递归时还没统计完),需要递归后才能确定方案数 // 所以采用递归后前传的方式,分解成子问题,每次递归后统计当前点为起点的方案数 cnt += dfs(a, b); } return cnt; }
intmain(){ // n || m:读入两个零时结束 // 注意这里先输入列数,再输入行数 while(scanf("%d%d", &m, &n), n || m){ int sx, sy; for (int i = 0; i < n; ++ i) scanf("%s", g[i]); for (int i = 0; i < n; ++ i) for (int j = 0; j < m; ++ j) if (g[i][j] == '@') sx = i, sy = j; // 找到起始点 // 初始化 memset(st, 0 ,sizeof st); printf("%d\n", dfs(sx, sy)); } return0; }
# DFS之连通性模型——AcWing 1113. 红与黑 from sys import setrecursionlimit
defmain(): setrecursionlimit(5000) defdfs(x, y): if x < 0or x >= n or y < 0or y >= m or vis[x][y]: return0 if g[x][y] == '#': return0 vis[x][y] = True return dfs(x - 1, y) + dfs(x + 1, y) + dfs(x, y - 1) + dfs(x, y + 1) + 1 # 注意不要忘了加1 # 注意不需要回溯时才能用这种尾递归 # 注意读取输入的方法 whileTrue: m, n = map(int, input().split()) # 注意审题,行在先还是列在先 ifnot (n + m): break g = [list(input()) for _ inrange(n)] vis = [[False] * m for _ inrange(n)] # 使用next获得起点位置 # next()方法从迭代器中检索下一个项目 sx, sy = next((x, y) for x inrange(n) for y inrange(m) if g[x][y] == '@') print(dfs(sx, sy)) if __name__ == "__main__": main()
int t, n, m; char g[N][N]; bool st[N][N]; // 注意由于这里我们需要恢复现场,所以递归完后st也会恢复最初状态,不用每次memset重新初始化 int ans; // 统计方案数的方法多种 // 一种是使用全局变量 // 一种是作为返回值,dfs内使用cnt,并在return时返回 // 一种是作为形参记录当前节点信息
voiddfs(int x, int y, int cnt){ // 判断是否满足边界条件 // 注意这里为了便于判断,引入的辅助变量cnt,用于记录已遍历的节点个数 // 之所以要记录是因为每个搜索方案都对应于一个从根节点到叶子节点的走法,必须判断何时到达叶子节点 // 参数是进行前传(作为返回值),还是后传(作为形参),还是使用全局变量: // 这里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; // 注意回溯后恢复现场 // 注意题目没有确定的终点,而是求遍历所有点的路径 // 因此每个方格中的点并不对应于搜索状态图中的节点,每个状态节点对应于一个从根节点到叶节点的走法/方案 // 因此不同方案时方格是可以被重复走的,且需要恢复状态。而标记数组的作用在于每个方案内部时方格只能走一次 // 也因为没有设定终点,所以需要自己判断是否到了叶子节点 }
defmain(): setrecursionlimit(5000) dir_x = [-2, -1, -1, -2, 2, 1, 1, 2] dir_y = [-1, -2, 2, 1, -1, -2, 2, 1] T = int(input()) # 使用形参后向传播遍历节点数信息,方便判断是否达到叶子节点 defdfs(x, y, cnt): ifnot cnt: return1 # 这里使用前向传参(传返回值)来记录路径总数结果 way = 0 vis[x][y] = True for dx, dy inzip(dir_x, dir_y): nx, ny = x + dx, y + dy # 注意这里python关系不等式写法 if0 <= nx < n and0 <= ny < m andnot vis[nx][ny]: way += dfs(nx, ny, cnt - 1) vis[x][y] = False return way for _ inrange(T): n, m, sx, sy = map(int, input().split()) vis = [[False] * m for _ inrange(n)] print(dfs(sx, sy, n * m - 1))
int n; string word[N]; // 存储所有单词 int g[N][N]; // 图不是直接存在的,而需要构建和存储 //为了使长度最长,重合的要最小即可。因此邻接矩阵g[N][N]除了存储两个单词是否有边,还要具体表示前一单词前缀和后一单词后缀的最小重合长度 int used[N]; // 记录每个单词使用次数 int ans; // 全局变量记录结果
// 传入接龙字符串和拼接的最后一个单词的索引 // 传入拼接的最后一个单词的索引是为了方便进行下一个单词的遍历搜寻 voiddfs(string dragon, int last){ ans = max((int)dragon.size(), ans); // 注意size()要强制转换为int,否则类型不对 // 更新字符串长度
used[last] ++; // 标记:使用次数加1 // 枚举所有单词 for (int i = 0; i < n; ++ i) if (g[last][i] && used[i] < 2) dfs(dragon + word[i].substr(g[last][i]), i); // 对于重复的问题,不用进行预先分割当复杂的操作 // 记录长度后直接用substr对应截取即可 used[last] --; // 回溯时恢复现场 // 同样不要放在for循环内,循环内仍是在对下一层进行枚举,不需要恢复当前状态 }
intmain(){ scanf("%d", &n); for (int i = 0; i < n; i ++ ) cin >> word[i]; char start; cin >> start; // scanf("\n%c", &start);
// 枚举所有单词,判断两两之间是否相接,并记录最小重合长度 // 这里注意由于每个单词可以使用两次,所以可以自己和自己相接 // 如果限制了不能自己和自己相接,遍历时要把自己挖出 for (int i = 0; i < n; ++ i) for (int j = 0; j < n; ++ j){ string a = word[i], b = word[j]; // 分别从前一个单词的结尾和后一个单词的开头进行枚举 // 注意使用string的substr函数直接截取字串,简化操作 for (int k = 1; k < min(a.size(), b.size()); ++ k) // 注意题意限制:重合长度必须大于等于1,且严格小于两个串的长度 // k < min(a.size(), b.size())即可限制严格小于两个串的长度 if (a.substr(a.size() - k, k) == b.substr(0, k)){ g[i][j] = k; break; } }
for (int i = 0; i < n; ++ i) if (word[i][0] == start) dfs(word[i], i); // 传入单词和其索引
# DFS之搜索顺序——AcWing 1117. 单词接龙 # 多入口DFS from sys import setrecursionlimit
defmain(): setrecursionlimit(5000) n = int(input()) g = [[0] * n for _ inrange(n)] word = [input() for _ inrange(n)] start = input() used = [0] * n ans = 0 defdfs(l, last): nonlocal ans # nonlocal声明的变量不是局部变量,也不是全局变量,而是外部嵌套函数内的变量 ans = max(ans, l) used[last] += 1 for i inrange(n): if g[last][i] and (used[i] < 2): dfs(l + len(word[i]) - g[last][i], i) used[last] -= 1 for i inrange(n): for j inrange(n): w1, w2 = word[i], word[j] for k inrange(1, min(len(w1), len(w2))): # if word[i][len(w1) - k:] == word[j][0:k]: if word[i].endswith(word[j][:k]): g[i][j] = k break # 多入口,根节点确定,但需要遍历其所有子节点 for i inrange(n): if word[i][0] == start: dfs(len(word[i]), i) print(ans) if __name__ == "__main__": main()
# DFS之搜索顺序——AcWing 1118. 分成互质组 # 静态数组的思路,为每个组找数字 from sys import setrecursionlimit
defmain(): n = int(input()) p = list(map(int, input().split())) group = [[0] * n for _ inrange(n)] vis = [False] * n ans = n defgcd(a, b): return gcd(b, a % b) if b else a defcheck(group, gc, i): for j inrange(gc): if gcd(p[group[j]], p[i]) > 1: returnFalse returnTrue defdfs(g, gc, tc, start): nonlocal ans if g >= ans: return if tc == n: ans = g return flag = True for i inrange(start, n): ifnot vis[i] and check(group[g], gc, i): vis[i] = True group[g][gc] = i flag = False dfs(g, gc + 1, tc + 1, i + 1) vis[i] = False ifnot gc: break if flag: dfs(g + 1, 0, tc, 0) dfs(1, 0, 0, 0) print(ans) if __name__ == "__main__": main()
# DFS之搜索顺序——AcWing 1118. 分成互质组 # 动态数组思路,为每个数字找组 from math import gcd # 返回最大公约数
defmain(): n = int(input()) p = list(map(int, input().split())) # 求出所有互质的一对数 coprime = {(x, y) for x in p for y in p if gcd(x, y) == 1} group = [[] for _ inrange(n)] res = n # 回溯法,为每个 p[idx] 找到合适的组 # idx为当前要分配的数的索引 # group 为预设的空组,size 为存在元素的组个数 defbacktrace(idx, size): nonlocal res # 剪枝 and 刷新答案 if size >= res or idx == n: res = min(res, size) return cur = p[idx] # 尝试将 p[idx] 插入当前存在元素的组(group 中前 size 个组) for g in group[:size]: # 查询当前 p[idx] 与组 g 中元素组成的二元组是否都在 coprime 中 ifall((cur, val) in coprime for val in g): g.append(cur) backtrace(idx + 1, size) g.pop() # 回溯恢复现场 # 新开一组,插入 p[idx] group[size] = [cur] backtrace(idx + 1, size + 1) group[size] = [] # 回溯恢复现场 backtrace(0, 0) print(res) if __name__ == "__main__": main()
# DFS之搜索顺序——AcWing 1118. 分成互质组 # 状压 DP from math import gcd
defmain(): n = int(input()) p = list(map(int, input().split())) # 求出所有互质的一对数 coprime = {(x, y) for x in p for y in p if gcd(x, y) == 1} dp = [n] * (1 << n) # 2^n # 使用二进制状态表示出所有可能的分组的情况 # 检查当前状态的所有元素是否满足组内互质 # all 中 iterator 为空时也返回 True,满足单个元素的答案情况 defhelper(mask): # 使用二进制保存当前组中包含的数,1为包含,0为不包含 # x >> i & 1用来判断第i位是0还是1 g = [p[idx] for idx inrange(n) if mask >> idx & 1] returnall((g[i], g[j]) in coprime for i inrange(len(g)) for j inrange(i + 1, len(g))) # 从小到大转移,枚举子集的状压 DP for i inrange(1, 1 << n): # 满足组内互质的二进制对应分组 if helper(i): dp[i] = 1 continue # 枚举一半子集即可,剪枝 j, split = (i - 1) & i, i >> 1 # 状压 DP while j > split: dp[i] = min(dp[i], dp[j] + dp[i^j]) j = (j - 1) & i print(dp[-1]) if __name__ == "__main__": main()