【蓝桥杯】43686-《路径之谜》

路径之谜

题目描述

  小明冒充 X 星球的骑士,进入了一个奇怪的城堡。城堡里边什么都没有,只有方形石头铺成的地面。假设城堡地面是 n×n 个方格。如下图所示。
在这里插入图片描述
图1

  按习俗,骑士要从西北角走到东南角。可以横向或纵向移动,但不能斜着走,也不能跳跃。每走到一个新方格,就要向正北方和正西方各射一箭。(城堡的西墙和北墙内各有 n 个靶子)同一个方格只允许经过一次。但不必走完所有的方格。如果只给出靶子上箭的数目,你能推断出骑士的行走路线吗?有时是可以的,比如上图中的例子。

  本题的要求就是已知箭靶数字,求骑士的行走路径(测试数据保证路径唯一)

输入描述

第一行一个整数 N (0≤N≤20),表示地面有 N×N 个方格。
第二行 N 个整数,空格分开,表示北边的箭靶上的数字(自西向东排列)
第三行 N 个整数,空格分开,表示西边的箭靶上的数字(自北向南排列)

输出描述

输出一行若干个整数,表示骑士的行走路径。

为了方便表示,我们约定每个小格子用一个数字代表,从西北角开始编号: 0,1,2,3 ⋯

比如,上图是一个4*4的方阵,则方阵中的方块编号为:
在这里插入图片描述

输入输出样例

示例

输入
4
2 4 3 4
4 3 3 3
输出
0 4 5 1 2 3 7 11 10 9 13 14 15

样例分析

第一个数字4代表这是一个4*4的方阵;
第二行的数字代表从西向东(从左向右)箭靶上的数字;
第三行的数字代表从北向南(从上向下)箭靶上的数字;
输出的结果代表骑士依次经过的方格的坐标。

解题思路

  我们观察一下路径、箭靶的关系:
在这里插入图片描述
  由题意可知,骑士从第一个方格0开始,每经过一个方格,向上方和左方各射出一箭,如果用 cntx = [2, 4, 3, 4] 表示上方的箭靶,用cnty = [4, 3, 3, 3] 代表左方的箭靶,那么上图中红色的数字代表骑士每一步走完之后,他在横向坐标轴上和纵向坐标轴的各个方向上经过格子的个数之和。(比如说,cntx [0] = 2, 说明骑士在第一个南北方向的纵列上总共经过了2个格子;同理,cnty [0] = 4,说明骑士在第一个东西方向的横列上总共经过了4个格子)。骑士每次经过的格子不能重复,那么,这就是一个路径遍历的问题,可以使用DFS深度优先或BFS广度优先算法遍历各种可能的路径。

  题目中,每走到一个新方格,就要向上方和左方各射一箭。我们已经知道了每个箭靶上箭的数量,那么逆向思维, 可以在搜索路径的过程中,骑士每走到一个新格子便拔掉对应箭靶上的箭。这样会有以下几个非常有趣的规律,来帮助我们构建程序:

  1. 骑士从起点开始,每经过一个格子,令北面和西面方向上各自箭靶上箭的数量-1,当走到终点后,北面和西面所有箭靶上的箭会被拔完;
  2. 骑士经过的格子数量(即路径的长度)为北面和西面方向上各自箭靶上箭的总数。比如在本案例中,该条路径的长度为13(即经过了13个格子)= 北面的箭总数(2+4+3+4)= 西面的箭总数(4+3+3+3)。
  3. 当某个方向上的箭被拔完之后,即该方向的箭数为0时,则不能继续向该方向前进,需要回溯然后尝试其他方向;如果所有方向上都不能继续前进,则需再退回上一格子寻找其他方向。
  4. 路径已访问过的格子标记为已访问,如果当前节点需要回溯,回溯时将当前格子的状态恢复为未访问,然后令北面和西面方向上各自箭靶上箭的数量+1。

所以,算法的计算过程为:
1,初始化

  • 定义常量 N = 404 表示最大矩阵大小。
  • 初始化路径数组 paths 用于存储路径,记录路径上的点。
  • 定义方向数组 dx 和 dy 分别表示四个移动方向(右、下、左、上)。
  • 初始化箭靶数组 cntx 和 cnty 用于存储每个箭靶上的箭数。
  • 初始化变量 n 表示矩阵大小,tot 表示箭的总数,success 表示是否找到成功路径。
  • 初始化二维布尔数组 visited[][] 用于标记某个位置是否被访问过。

2,输入处理

  • 从输入读取矩阵大小 n。
  • 读取并存储每个箭靶在 x 方向上箭的数量到 cntx 数组,并累加到 tot 中。
  • 读取并存储每个箭靶在 y 方向上箭的数量到 cnty 数组,并累加到 tot 中。

3,使用深度优先搜索DFS算法寻找路径

  • 定义递归函数 dfs(x, y, num) 进行深度优先搜索:
  • 将当前点 (x, y) 的编号记录到 paths 数组中。
  • 将当前位置标记为已访问。
  • 减少对应箭靶上的箭数量。
  • 检查是否到达终点且所有箭都被拔掉。如果是,则设置 success 为 True 并返回。
  • 否则,尝试向四个方向移动(右、下、左、上),并对每个可能的位置进行递归调用 dfs。
  • 在递归调用后恢复状态(回溯),即将当前位置标记为未访问,并增加对应箭靶上的箭数量。

4,检查结束条件

  • 定义函数 check() 检查所有箭靶上的箭是否都被拔掉。如果所有箭靶上的箭都为零,则返回 True,否则返回 False。

5,输出结果

  • 调用 dfs(1, 1, 1) 从起点开始进行深度优先搜索。
  • 如果找到成功路径,则输出路径上的点编号。

代码实现

Python 实现:

# 定义常量 N,表示最大矩阵大小
N = 404

# 记录路径
paths = [0] * N
# 控制走的方向,分别为右、下、左、上
dx = [1, 0, -1, 0]
dy = [0, 1, 0, -1]
# 记录两个方向上每个箭靶中箭的数量
cntx = [0] * N
cnty = [0] * N
# 矩阵的大小
n = 0
tot = 0
# 是否到达终点
success = False
# 标记某点是否走过
visited = [[False for _ in range(N)] for _ in range(N)]

def check():
    # 遍历每个箭靶,检查箭是否都已被拔掉
    for i in range(1, n + 1):
        if cntx[i] != 0 or cnty[i] != 0:
            return False
    return True

def dfs(x, y, num):
    global success
    # 将该点编号记录到路径中
    paths[num] = (y - 1) * n + x - 1
    # 将该点标记为已经走过的状态
    visited[x][y] = True
    # 拔掉对应的箭
    cntx[x] -= 1
    cnty[y] -= 1
    # 判断是否到达终点
    if x == n and y == n and check():
        success = True
        return
    # 上下左右四个方向搜索下一步
    for i in range(4):
        xx = x + dx[i]
        yy = y + dy[i]
        # 没有到达终点,下一步(xx,yy)未走过且在地图范围内
        if not success and 1 <= xx <= n and 1 <= yy <= n and not visited[xx][yy]:
            # 该点对应箭靶上有箭,说明该点可以走
            if cntx[xx] > 0 and cnty[yy] > 0:
                # 搜索下一步
                dfs(xx, yy, num + 1)
                # 复原,回溯
                visited[xx][yy] = False
                cntx[xx
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

清弦墨客

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

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

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

打赏作者

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

抵扣说明:

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

余额充值