Python | 蓝桥杯进阶第六卷——搜索

在这里插入图片描述

欢迎交流学习~~


专栏: 蓝桥杯Python组刷题日寄


蓝桥杯进阶系列:

🏆 Python | 蓝桥杯进阶第一卷——字符串
🔎 Python | 蓝桥杯进阶第二卷——贪心
💝 Python | 蓝桥杯进阶第三卷——动态规划
✈️ Python | 蓝桥杯进阶第四卷——图论
🌞 Python | 蓝桥杯进阶第五卷——数论
💎 Python | 蓝桥杯进阶第六卷——搜索


🎁 危险系数

题目:
时间限制:
1s

内存限制:
128MB

题目描述:
抗日战争时期,冀中平原的地道战曾发挥重要作用。
地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。
我们来定义一个危险系数 DF(x,y)
对于两个站点 x y (x != y), 如果能找到一个站点 z,当 z 被敌人破坏后,xy 不连通,那么我们称 z 为关于 x,y 的关键点。相应的,对于任意一对站点 xy,危险系数 DF(x,y) 就表示为这两点之间的关键点个数。
本题的任务是:已知网络结构,求两站点之间的危险系数。

输入描述:
输入数据第一行包含 2 个整数 n(2 <= n <= 1000), m(0 <= m <= 2000) ,分别代表站点数,通道数;
接下来 m 行,每行两个整数 u,v (1 < = u, v < = n; u != v) 代表一条通道;
最后 1 行,两个数 u,v,代表询问两点之间的危险系数 DF(u, v)

输出描述:
一个整数,如果询问的两点不连通则输出 -1.

样例输入:

7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6

样例输出:
2


解题思路

深度优先搜索,对于每条从 uv 的路径,对于路径上经过的每个结点 i,其到达 v 的路径数 path[i] 都记 +1,搜索结束后若 path[i]==path[v] 则说明这个点为关键点。

具体思路见参考代码及其注释。

参考代码

# 深度优先搜索
def dfs(u, v):
    global path
    # 退出条件
    if u == v:
        # 当访问到最后的站点 end
        for i in range(n):
            # 遍历所有站点
            if visited[i] == True:
                # 如果这个站点在这条路线上
                path[i] += 1
        return
    for i in range(n):
        if mat[u][i] == 1 and not visited[i]:
            # 当 start 和 i 两站点连通
            # 且站点 i 未被访问
            visited[i] = True
            # 递归
            dfs(i, v)
            # 回溯
            visited[i] = False


if __name__ == '__main__':
    n, m = map(int, input().split())
    # mat 为邻接矩阵
    mat = [[0 for j in range(n)] for i in range(n)]
    for i in range(m):
        a, b = map(int, input().split())
        mat[a-1][b-1] = 1
        mat[b-1][a-1] = 1

    # u, v 两个节点
    u, v = map(int, input().split())
    u, v = u-1, v-1

    # visited[i] 表示站点 i 是否被访问
    visited = [False for i in range(n)]
    # path[i] 表示经过站点 i 到终点 v 的路径数
    path = [0 for i in range(n)]

    dfs(u, v)
    # path[v]为从 u 到 v 的路径数
    # 当其他站点也为这个数时,说明该站点为关键点
    print(path.count(path[v]) - 1)
    # 这里只需要 -1 是表示 v 结点
    # 而不用考虑 u 结点,是因为我们设置邻接矩阵时有 mat[i][i] = 0

🌲 穿越雷区

题目:
时间限制:
1s

内存限制:
128MB

题目描述:
X 星的坦克战车很奇怪,它必须交替地穿越正能量辐射区和负能量辐射区才能保持正常运转,否则将报废。
某坦克需要从 A 区到 B 区去(AB 区本身是安全区,没有正能量或负能量特征),怎样走才能路径最短?

已知的地图是一个方阵,上面用字母标出了 AB 区,其它区都标了正号或负号分别表示正负能量辐射区。
例如:

A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -

坦克车只能水平或垂直方向上移动到相邻的区。

输入描述:
输入第一行是一个整数 n,表示方阵的大小, 4<=n<100
接下来是 n 行,每行有 n 个数据,可能是 AB+- 中的某一个,中间用空格分开。
AB 都只出现一次。

输出描述:
要求输出一个整数,表示坦克从A区到B区的最少移动步数。
如果没有方案,则输出-1

样例输入:

5
A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -

样例输出:
10


解题思路

本题要利用 广度优先搜索 的知识。

其中用 visiteded 来记录结点是否被访问过。
res 来记录每层搜索结点的坐标和到达该结点的最少移动次数。

其搜索结构可以用如下伪代码描述:

while res:
	# 退出条件
	if 到达目标结点:
		break
	
	# 遍历四个搜索方向
	for i in range(4):
		if 满足所需要求:
			将该结点相关信息添加到 res 中
	
	# 该结点搜索完毕,删去信息
	res.pop(0)

参考代码

# 广度优先搜索
# 输入数据
n = int(input())
cell = [list(input().split()) for i in range(n)]

# visited 记录该点是否被访问过
visited = [[False for j in range(n)] for i in range(n)]
visited[0][0] = True

# dx dy 表示向上下左右四个方向移动时的坐标变换
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

# res 用来记录当前(搜索)层各结点的坐标和到达该结点的最少移动次数
res = [[0, 0, 0]]
while res:
    # 终止条件:到达目标结点
    if cell[res[0][0]][res[0][1]] == 'B':
        print(res[0][2])
        break

    # 广度优先搜索,遍历四个方向
    for i in range(4):
        # 新坐标
        new_x = res[0][0] + dx[i]
        new_y = res[0][1] + dy[i]

        # 如果新结点的坐标不超过边界
        if (0 <= new_x <= n-1) and (0 <= new_y <= n-1):
            # 新结点的坐标坐标没有被访问过
            # 并且新旧两个结点的状态不同,即不能是同一个正负状态
            if (not visited[new_x][new_y]) and cell[res[0][0]][res[0][1]] != cell[new_x][new_y]:
                # 添加该结点到res
                res.append([new_x, new_y, res[0][2]+1])
                visited[new_x][new_y] = True
    # 该结点搜索完毕
    res.pop(0)

# 如果最后不能到达 B 结点
# 此时 res 为空
if len(res) == 0:
    print('-1')

🚀 地宫取宝

题目:
时间限制:
1s

内存限制:
128MB

题目描述:
X 国王有一个地宫宝库。是 n × m n \times m n×m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。

地宫的入口在左上角,出口在右下角。

小明被带到地宫的入口,国王要求他只能向右或向下行走。

走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。

当小明走到出口时,如果他手中的宝贝恰好是k件,则这些宝贝就可以送给小明。

请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这k件宝贝。

输入描述:
输入一行 3 个整数,用空格分开:n m k (1< =n,m< =50, 1< =k< =12)

接下来有 n 行数据,每行有 m 个整数 Ci (0< =Ci< =12) 代表这个格子上的宝物的价值

输出描述:
要求输出一个整数,表示正好取 k 个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 取模的结果。

样例输入:

2 3 2
1 2 3
2 1 5

样例输出:
14


解题思路

深度优先搜索 + 动态规划。

创建 dp 数组,其中 dp[i][j][k][l] 表示坐标为 (i,j) 点物品数量为 k, 最大价值为 l 的方案数。

具体思路见参考代码和注释。

参考代码

# 深度优先搜索
def dfs(x, y, loads, max_value):
    global n, m, k, cell, dp

    # 该结点已经访问过
    if dp[x][y][loads][max_value+1] != -1:
        return dp[x][y][loads][max_value+1]

    # 如果到达了终点
    if (x == n-1) and (y == m-1):
        # 若此时物品数量已经为 k, 即已满
        # 或者终点物品价值比最大价值还要大
        if (loads == k) or (loads == k-1 and cell[x][y] > max_value):
            dp[x][y][loads][max_value+1] = 1
            return 1
        else:
            dp[x][y][loads][max_value+1] = 0
            return 0

    # count 用来记录方案数
    count = 0
    # 取出当前位置的物品价值
    current_value = cell[x][y]

    d = [[0, 1], [1, 0]]
    # 遍历右和下两个方向
    for i in range(2):
        # 移动后的新坐标
        new_x = x + d[i][0]
        new_y = y + d[i][1]

        # 如果新坐标没有超出边界
        if (new_x < n) and (new_y < m):
            # 如果当前位置的物品可以拿起
            if (current_value > max_value) and (loads < k):
                # count 要加上如下搜索的方案数:
                # 从当前位置开始搜索, 物品数量为 loads+1, 最大价值为 current_value
                count = count + dfs(new_x, new_y, loads+1, current_value)
            # 还要加上不拿起该位置的物品的方案数
            count = count + dfs(new_x, new_y, loads, max_value)

    # 赋值
    dp[x][y][loads][max_value+1] = count

    return dp[x][y][loads][max_value+1]



if __name__ == '__main__':
    n, m, k = map(int, input().split())
    cell = [list(map(int, input().split())) for i in range(n)]
    # dp[i][j][k][l] 表示坐标为 (i,j) 点物品数量为 k, 最大价值为 l 的方案数
    dp = [[[[-1 for _ in range(14)] for _ in range(13)] for j in range(m+1)] for i in range(n+1)]

    # 输出最后结果
    # 从 (0, 0) 开始搜索, 此时物品数量为 0, 最大价值设为负数
    print(dfs(0, 0, 0, -1) % 1000000007)

💡 2n皇后问题

题目:
时间限制:
1s

内存限制:
128MB

题目描述:
给定一个 n*n 的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入 n 个黑皇后和 n 个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n 小于等于 8

输入描述:
输入的第一行为一个整数 n,表示棋盘的大小。 n 小于等于 8
接下来 n 行,每行 n01 的整数,如果一个整数为 1,表示对应的位置可以放皇后,如果一个整数为 0,表示对应的位置不可以放皇后。

输出描述:
输出一个整数,表示总共有多少种放法。

样例输入:


4
1 1 1 1 
1 1 1 1 
1 1 1 1 
1 1 1 1 

样例输出:
2


解题思路

根据题意我们得到:每行必然有一个黑皇后和一个白皇后。
我们的思路是:先将各行的黑皇后放好,再将各行的白皇后放好。

定义 s 为皇后状态,s=2 为黑皇后,s=3 为白皇后。
即对于 cell 表示各结点状态:有 0 表示不可以放皇后,1 表示可以放皇后,2 表示黑皇后,3 表示白皇后。

定义 check(row, col, s, cell) 函数,用来判断坐标 (row, col)s 对应的皇后是否可行。
定义深度优先搜索函数 dfs(row, n, s, cell) 搜索第 row 行(最多有 n 行)是否可以放 s 对应的皇后。

参考代码

n = int(input())
cell = [list(map(int, input().split())) for i in range(n)]
count = 0

# check 函数,判断皇后放的位置是否满足条件
def check(row, col, s, cell):
    # 检查对应列是否有 s
    for i in range(row-1, -1, -1):
        if cell[i][col] == s:
            return False
    # 检查左对角线
    r = row - 1
    c = col - 1
    while r >= 0 and c >= 0:
        if cell[r][c] == s:
            return False
        r -= 1
        c -= 1

    # 检查右对角线
    r = row - 1
    c = col + 1
    while r >= 0 and c < n:
        if cell[r][c] == s:
            return False
        r -= 1
        c += 1
    return True

# dfs 函数
def dfs(row, n, s, cell):
    global count
    # 判断是否到最后一行
    # 这里是 n 表示,row == n-1 时已经搜索完毕
    # 再次递归时有 row == n,这是说明已经到底
    if row == n:
        # 若为黑皇后
        if s == 2:
            # 则接下来从第一行开始放白皇后
            dfs(0, n, 3, cell)
        # 若为白皇后
        # 此时黑白皇后已经全部放完
        # 退出
        if s == 3:
            count += 1
        return

    # 遍历每行
    for col in range(n):
        # 如果 cell[row][col] 不能再放皇后
        if cell[row][col] != 1:
            continue

        # 如果该结点可以放皇后
        if check(row, col, s, cell):
            # 改变结点状态
            cell[row][col] = s
            # 递归搜索下一行
            dfs(row+1, n, s, cell)
            # 回溯
            cell[row][col] = 1

if __name__ == '__main__':
    dfs(0, n, 2, cell)
    print(count)

💖 学霸的迷宫

题目:
时间限制:
1s

内存限制:
128MB

题目描述:
学霸抢走了大家的作业,班长为了帮同学们找回作业,决定去找学霸决斗。但学霸为了不要别人打扰,住在一个城堡里,城堡外面是一个二维的格子迷宫,要进城堡必须得先通过迷宫。因为班长还有妹子要陪,磨刀不误砍柴功,他为了节约时间,从线人那里搞到了迷宫的地图,准备提前计算最短的路线。可是他现在正向妹子解释这件事情,于是就委托你帮他找一条最短的路线。

输入描述:
第一行两个整数 nm,为迷宫的长宽。
接下来n行,每行 m 个数,数之间没有间隔,为 01 中的一个。0 表示这个格子可以通过,1 表示不可以。假设你现在已经在迷宫坐标 (1,1) 的地方,即左上角,迷宫的出口在 (n,m) 。每次移动时只能向上下左右 4 个方向移动到另外一个可以通过的格子里,每次移动算一步。数据保证 (1,1)(n,m) 可以通过。

输出描述:
第一行一个数为需要的最少步数 K
第二行 K 个字符,每个字符 ∈{U,D,L,R},分别表示上下左右。如果有多条长度相同的最短路径,选择在此表示方法下字典序最小的一个。

样例输入:

3 3
001
100
110

样例输出:

4
RDRD

解题思路

本题利用广度优先搜索。

注意题目中要求:

如果有多条长度相同的最短路径,选择在此表示方法下字典序最小的一个。

因此我们搜索的顺序是 D, L, R, U,即:下左右上

思路与上文中穿越雷区相同。

参考代码

# 广度优先搜索
n, m = map(int, input().split())
cell = [list(map(int, input())) for i in range(n)]

# visited用来记录结点访问状态
visited = [[False for j in range(m)] for i in range(n)]
visited[0][0] = True

# dx, dy 表示 下左右上 方向的坐标变动
d = ['D', 'L', 'R', 'U']
dx = [1, 0, 0, -1]
dy = [0, -1, 1, 0]

# res 用来记录当前搜索层结点的结果
# res[0][0] 表示横坐标
# res[0][1] 表示纵坐标
# res[0][2] 表示到达该结点的移动次数
# res[0][3] 表示到达该结点的路径
res = [[0, 0, 0, []]]

while res:
    # 终止条件:到达目标结点
    if (res[0][0] == n-1) and (res[0][1] == m-1):
        print(res[0][2])
        print(''.join(res[0][3]))

    # 遍历四个方向
    for i in range(4):
        new_x = res[0][0] + dx[i]
        new_y = res[0][1] + dy[i]
        new_path = res[0][3] + [d[i]]

        # 如果新坐标不超过边界
        if (0 <= new_x <= n-1) and (0 <= new_y <= m-1):
            # 如果该结点没有被访问过,且可以通过
            if (not visited[new_x][new_y]) and cell[res[0][0]][res[0][1]] == 0:
                res.append([new_x, new_y, res[0][2]+1, new_path])
                visited[new_x][new_y] = True

    # 该结点搜索完毕
    res.pop(0)

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四口鲸鱼爱吃盐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值