题目描述
大楼的每一层楼都可以停电梯,而且第i层楼(1<=i<=N)上有一个数字Ki(0<=Ki<=N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如:3 3 1 2 5代表了Ki(K1=3,K2=3,……),从一楼开始。在一楼,按“上”可以到4楼,按“下”是不起作用的,因为没有-2楼。那么,从A楼到B楼至少要按几次按钮呢?
输入
输入文件共有二行,第一行为三个用空格隔开的正整数,表示N,A,B(1≤N≤200, 1≤A,B≤N),第二行为N个用空格隔开的正整数,表示Ki。
输出
输出文件仅一行,即最少按键次数,若无法到达,则输出-1。
样例输入
5 1 5
3 3 1 2 5
样例输出
3
解决这个问题我们可以先读入输入数据,并根据题目要求,计算任意两层楼之间的最短距离,即距离矩阵d[i][j]。
由于电梯只有上下两个按钮,所以任意两层楼之间的最短路程需要乘以2才是真正的最少按键次数。所以我们可以得到从A楼到B楼的最少按键次数为:
需要注意的是,当A楼或B楼上没有电梯时,无法到达目标楼层,此时应输出-1。
另外,这个问题在最坏情况下需要计算n的三次方次,时间复杂度较高,如果数据规模很大的话,可能需要考虑优化算法。
输入部分:
n, a, b = map(int, input().split())
k = list(map(int, input().split()))
初始化距离矩阵和访问列表:
d = [[float('inf')] * n for _ in range(n)]
vis = [False] * n
计算任意两层楼之间的最短距离:
for i in range(n):
vis[i] = True
for j in range(n):
if vis[j]:
d[i][j] = abs(i-j)
vis[i] = False
for k1 in range(n):
for k2 in range(n):
for k3 in range(n):
d[k2][k3] = min(d[k2][k3], d[k2][k1]+d[k1][k3])
计算从A楼到B楼的最少按键次数:
if k[a-1] == 0 or k[b-1] == 0:
print(-1)
else:
min_press = d[a-1][b-1]*2 + max(k[a-1], k[b-1])
print(min_press)
完整代码如下:
n, a, b = map(int, input().split())
k = list(map(int, input().split()))
# 初始化距离矩阵和访问列表
d = [[float('inf')] * n for _ in range(n)]
vis = [False] * n
# 计算任意两层楼之间的最短距离
for i in range(n):
vis[i] = True
for j in range(n):
if vis[j]:
d[i][j] = abs(i-j)
vis[i] = False
for k1 in range(n):
for k2 in range(n):
for k3 in range(n):
d[k2][k3] = min(d[k2][k3], d[k2][k1]+d[k1][k3])
# 计算从A楼到B楼的最少按键次数
if k[a-1] == 0 or k[b-1] == 0:
print(-1)
else:
min_press = d[a-1][b-1]*2 + max(k[a-1], k[b-1])
print(min_press)
有人问:“只有这一种方法吗?”当然不止这一种方法(你个小淘气)。
我们也可以使用队列来实现广度优先搜索,也可以得到正确的答案。
使用队列来实现广度优先搜索的优点是:使用了队列来存储待搜索的节点,并且在处理过程中只对未访问的节点进行处理,从而避免了重复计算。这种方法的时间和空间复杂度都较为稳定,并且可以同时计算多组数据。
下面展示我的初始代码:
import queue
n,a,b=map(int,input().split())
l=[0]+list(map(int,input().split()))
c=[0]*(n+1)
def right(x):
if x>=1 and x<=n and c[x]==0:
return True
else:
return False
q=queue.Queue()
def d(x):
q.put((x,0))
c[x]=1
while not q.empty():
now=q.get()
x=now[0]
if now[0]==b:
print(now[1])
return
x1=x+l[x]
x2=x-l[x]
if right(x1):
q.put((x1,now[1]+1))
c[x1]=1
if right(x2):
q.put((x2,now[1]+1))
c[x2]=1
print(-1)
d(a)
我们观察代码后不难发现这段代码有许多地方可以进行优化(我自己当然也发现了)
1.我使用了一个长度为n+1的列表c来记录每个楼层是否被访问过,然而我并没有充分利用输入数组l已有的信息,在使用新的数组时应尽量考虑复用已有的数据。在本题中我们可以将访问状态一并保存在输入数组l中。
2.在right函数中判断某个楼层是否能够停电梯的方式,我们可以将其改进成更加简洁的语法:
def right(x):
return 1 <= x <= n and not c[x]
3.可以使用元组来存储当前楼层和距离起始点的距离,这样可以方便地把当前结果传递给下一步搜索。
下面是经过优化后的代码:
# 导入queue模块,用于实现队列数据结构。
import queue
# 读入输入数据(n表示楼层数量,a表示起始楼层,b表示目标楼层,l为每个楼层可以到达的最大层数)
n, a, b = map(int, input().split())
l = [0] + list(map(int, input().split()))
# 使用Queue类来创建一个列队
q = queue.Queue()
# 将起始节点加入队列中(x表示当前搜索的楼层,dist表示距离起始点的距离,visited表示各楼层的访问状态)
q.put((a, 0, [False]*(n+1)))
# 当队列不为空时进行循环,不断取出队中元素进行处理
while not q.empty():
# 取出队列中的第一个元素,包含当前楼层、距离和访问状态
now = q.get()
x, dist, visited = now
# 判断当前楼层是否为目标楼层,如果是则输出距离并跳出循环
if x == b:
print(dist)
break
# 如果当前楼层未被访问,则将其标记为已访问,并检查相邻楼层能否到达
if not visited[x]:
visited[x] = True
# 如果向上走能够到达新楼层,则将新楼层加入队列中继续搜索
if right(x+l[x], visited):
q.put((x+l[x], dist+1, visited.copy()))
# 如果向下走能够到达新楼层,则将新楼层加入队列中继续搜索
if right(x-l[x], visited):
q.put((x-l[x], dist+1, visited.copy()))
else:
# 如果队列为空且未找到目标楼层,则无法到达目标楼层,输出-1
print(-1)
# 判断某个楼层是否可以停电梯的函数
def right(x, visited):
return 1 <= x <= n and not visited[x]
在更新后的代码中,我们使用了元组来存储当前楼层、距离起始点的距离以及访问状态。每当取出队列中的一个元素时,我们先检查是否到达了目的地,如果是,则输出此时的距离;否则,我们计算当前节点可以到达的相邻楼层,并将这些节点加入队列中等待搜索。同时,我们还优化了判断某个节点是否已经被访问的方式,使用visited.copy()函数来创建一个新的列表,避免修改原有状态。
感谢您的阅读!