本文章集合了个人认为的DFS中一些经典的题目并给出题解与思想,用于学习过程中复习。众所周知,DFS是数据结构中较为基础的搜索算法,当刷题量足够大时,就能对递归、回溯、剪枝有较好的理解,初期不理解的话可以在草稿纸上模拟。
1.树的直径问题(基于模板题大臣的旅费)
题目描述:
很久以前,T 王国空前繁荣。为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。
为节省经费,T 国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。
J 是 T 国重要大臣,他巡查于各大城市之间,体察民情。所以,从一个城市马不停蹄地到另一个城市成了 J 最常做的事情。他有一个钱袋,用于存放往来城市间的路费。
聪明的 J 发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第 x 千米到第 x +1 千米这一千米中( x 是整数),他花费的路费是 x +10 这么多。也就是说走 1 千米花费 11,走 2 千米要花费 23。
J 大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?
输入描述:
输入的第一行包含一个整数 n,表示包括首都在内的 T 王国的城市数。
城市从 1 开始依次编号,1 号城市为首都。
接下来 n -1 行,描述 T 国的高速路( T 国的高速路一定是 n -1 条)。
每行三个整数 Pi,Qi,Di,表示城市 Pi 和城市 Qi 之间有一条高速路,长度为 Di 千米。
输出描述:
输出一个整数,表示大臣 J 最多花费的路费是多少。
这题就不多赘述,有一个视频讲的很详细,参考了博主py小郑的文章,题解链接见树的直径讲解
大概的思想就是从任意一个点找到一个最远距离的点,这个点就是树直径的一个端点,从端点出发则可以找到另一个端点并给出树的直径。
AC_code
def dfs(x):
vis[x] = 1
for i in range(1,n+1):
if not vis[i] and mp[x][i] != 0:
ans[i] = ans[x] + mp[x][i]
dfs(i)
def costfun(d):
return int(d * 10 + d*(d+1) / 2)
n = int(input())
mp = [[0]*(n+1) for _ in range(n+1)]
for i in range(n-1):
x,y,d = map(int,input().split())
mp[x][y] = d
mp[y][x] = d
vis = [0] * (n+1)
ans = [0] * (n+1)
dfs(1)
q = ans.index(max(ans))
vis = [0] * (n+1)
ans = [0] * (n+1)
dfs(q)
print(costfun(max(ans)))
2.移动字母
题目描述:移动字母(蓝桥OJ)
这道题的思想就简单很多,化繁为简,将移动字母替换为移动空格(这道题很类似跳蚱蜢(第八届省赛)那道题,有兴趣的朋友可以去蓝桥OJ上看看).
然后重要的一点就是用字典去存储所有可能的情况。
这道题还用到一个,坐标如何在一维数组以及二维数组间转换的方法(大家完全可以在草稿纸上模拟一下,这个很简单很容易看懂)
AC_code
def dfs(s):
k1 = list(s)
space_index = s.index('*')
x,y = space_index // 3,space_index % 3
for i in dir:
nx,ny = x+i[0],y+i[1]
if nx in [0,1] and ny in [0,1,2]:
old = k1.copy()
old[nx * 3 + ny],old[space_index] = old[space_index],old[nx*3+ny]
if ''.join(old) not in ans.keys():
ans[''.join(old)] = ans[s] + 1
dfs(''.join(old))
n = int(input())
ans = dict()
dir = [(-1,0),(1,0),(0,1),(0,-1)]
s = 'ABCDE*'
ans[s] = 0
dfs(s)
for i in range(n):
s = input()
if s in ans.keys():print(1)
else: print(0)
3.连通性判断(基于全球变暖问题)
题目描述:全球变暖(蓝桥OJ)
这道题用到一个连通性判断,判断一个连通的块是否存在。拿全球变暖这道题来说,如若在搜索的过程中发现某个点上下左右四个方向皆为陆地,就可以判断这个点无法被淹没。于是我们搜索的任务就变成了,寻找有多少个岛屿无法被淹没,这样就又变成了经典的DFS。
AC_code
def dfs(x,y):
vis[x][y] = 1
global ans
if mp[x+1][y] == '#' and mp[x-1][y] == '#' and mp[x][y+1] == '#' and mp[x][y-1] == '#':
ans = 1
for i in dir:
nx,ny = x + i[0],y + i[1]
if 0<=nx<n and 0<=ny<n:
if not vis[nx][ny] and mp[nx][ny] == '#':
vis[nx][ny] = 1
dfs(nx,ny)
n = int(input())
mp = []*(n+1)
for i in range(n):
mp.append(input())
# print(mp)
dir = [(-1,0),(1,0),(0,1),(0,-1)]
rns = 0
vis = [[0] * (n + 1) for _ in range(n + 1)]
for i in range(n):
for j in range(n):
if mp[i][j] == '#' and not vis[i][j]:
ans = 0
dfs(i,j)
if not ans: rns += 1
print(rns)
4.分考场(DFS变形)
题目描述:分考场
这道题我一开始就想到了并查集,但有些无法下手,但从另一个角度来看,将每个人都给予一个房间,并在搜索的过程中检查一下是否符合条件,或许也能成为一种思路。
AC_code
maxn = 1005
ans = 10000000
a = [[0 for _ in range(maxn)] for _ in range(maxn)]# 用于储存关系,1就是关联
c = [0 for _ in range(maxn)]# 表示第i个屋子里面的人数
f = [[0 for _ in range(maxn)] for _ in range(maxn)]# 表示第i个房间的第j个人的编号
def dfs(x, tot):# x表示是当前判断的第i个同学,tot是表示用掉的屋子数
global ans
if tot >= ans:# 进行剪枝
return
if x == n+1:# 如果已经搜完了n个人,进行判断
ans = min(ans, tot)
return
for i in range(1, tot+1):# 对已有的屋子进行遍历
lens = c[i]
k = 0
for j in range(1, lens+1):
if not a[x][f[i][j]]:# 如果这两个人没有关系,k+1
k += 1
if k == lens:# 如果屋子里的人都没有与x相关,把x放进屋子里面
c[i] += 1
f[i][c[i]] = x
dfs(x+1, tot)
c[i] -= 1#回溯
c[tot+1] += 1
f[tot+1][c[tot+1]] = x
dfs(x+1, tot+1)# 回溯
c[tot+1] -= 1
n = int(input())
m = int(input())
for i in range(m):
x, y = input().split(' ')
x = int(x)
y = int(y)
a[x][y] = 1
a[y][x] = 1
dfs(1, 0)
print(ans)
分考场的代码参考了别的博主的代码。
5.排列序数
题目描述:排列序数
这题的思想很简单,就是一个用dfs书写排列的模板。然而dfs大概有两种书写排列的模板,一种是在dfs过程中对数组进行交换,但无法得到字典序,一种就是用vis[i]数组记录dfs的过程(可参考之前一篇文章数字游戏)。那么这道题则是采用第二种方法,得到的数组采用字典存储。
def dfs(s,t):
global rns
if s == t:
tmp = ''.join(b[0:t])
ans[tmp] = rns
rns += 1
else:
for i in range(t):
if not vis[i]:
vis[i] = 1
b[s] = a[i]
dfs(s+1,t)
vis[i] = 0
a = ['a','b','c','d','e','f','g','h','i','j']
vis,b = [0]*10,[0]*10
str1 = input()
ans = dict()
rns = 0
dfs(0,len(str1))
print(ans[str1])