第1题-找到通信质量最高的基站
题目内容
闹市区中有一条马路,马路从号路口开始,到号路口结束,在每个路口都架设了最新技术的通信基站,每个基站的信号可以覆盖前后各个路口的范围,即第i个路口上的基站,可以覆盖这两个路口之间的马路,因此用户的手机处于多个基站的覆盖范围中。每个基站会统计当前接入人数,为保障最佳通信质量,用户手机应选择连接人数最少的基站进行通讯。
这条马路一共个路口,小明从号路口出发向前走,求小明在每个路段中的最佳通讯基站。不考虑处于路口中间的特殊场景,只考虑在每个路段中的场景,例如第个路段应为号路口到号路口之间的路段,如果基站覆盖范围,此时最佳基站应为、、中连接人数最少的基站。
输入描述
输入为两行
第一行长度为N的整数数组,数组元素以空格分隔,其中表示号路口基站的当前接入人数。数组长度,
第二行为基站覆盖范围,
非法输入返回
输出描述
返回一个数组,表示路段上最佳基站的编号,数组元素之间以空格分隔。例如号路口到号路口的路段,为号路段,其最佳基站用表示。
样例1
输入
3 5 8 7 6 7 4
2
输出
0 0 1 4 6 6
说明
小明在第段路时,位于号和号基站中间,此时可以连接到、、这个基站,而这三个基站中连接人数最小的是号基站(个人),因此输出数组第一个元素应为号基站。小明位于第段路时,位于号和号基站中间,此时可以连接到、、、这个基站,选择其中连接人数最小的基站,即号基站(人),因此输出数组第二个元素为号基站。以此类推。
样例2
输入
9 8 7 6 7 8 9
2
输出
2 3 3 3 3 4
说明
小明在第1段路时,可以连接到的基站为、、这个基站,其中连接人数最小的基站为号基站(人连接),因此输出数组第一个元素为号基站。以此类推。
思路
要在每个路段上快速求出滑动区间内的最小值位置,可用双端队列(deque)维护“单调队列”,以支持区间左右端点同时移动的场景:
-
定义区间端点
-
第 号路段的左端点:
-
右端点:
-
-
初始化
-
对应第 号路段,区间为 ,将所有下标依次入队,同时维护队内从队首到队尾对应的 值呈单调递增。
-
-
滑动更新
对每个后续路段 (从 到 ):-
右端扩展:若 ,将新的下标 按“单调”规则加入队尾。
-
左端收缩:若队首元素的下标 $idx
-
队首即最小:此时队首存储的下标即为区间内最小值位置,令 队首下标。
-
整个过程对每个下标最多入队一次、出队一次,时间复杂度为 。
import sys
from collections import deque
def best_station(a, k):
n = len(a)
if n < 2 or not (1 <= k <= n): return
dq, res = deque(), []
for i in range(n-1):
R = min(n-1, i+k)
if i == 0:
for x in range(R+1):
while dq and a[x] <= a[dq[-1]]: dq.pop()
dq.append(x)
else:
if R > dq[-1]:
for x in range(dq[-1]+1, R+1):
while dq and a[x] <= a[dq[-1]]: dq.pop()
dq.append(x)
L = i+1-k
while dq and dq[0] < L: dq.popleft()
res.append(dq[0])
return res
data = sys.stdin.read().split()
a, k = list(map(int, data[:-1])), int(data[-1])
r = best_station(a, k)
if r is None:
print(-1)
else:
print(*r)
第2题-游园线路
题目内容
某公园每年都会在新年时举办灯会,由于公园面积很大目各景点分散,希望你设计一条游园线路,从某个指定入口景点开始,到早个指定出口景点结束,使得游园总路程最短。最短路线不需要走完所有的景点,目中间允许经过其他出入口景点而不离开公园。
输入描述
第一行:N,景点个数,景点序号从开始,结束。
第二行至第行:是一个的矩阵,表示各相邻景点之间的距离,距离为表示不相邻。
第行:该景点是否是公园出入口(-是,-否)。
第行:要计算最短线路的入口景点序号和出口景点序号
所有用例的输入确保一定存在符合条件的线路,你无需考虑无解的情况。
输出描述
具体游园线路,如果有多条符合条件的线路,按景点序号从小到大进行排序。
样例1
输入
3
0 2 4
2 0 3
4 3 0
1 0 1
0 2
输出
0 2
说明
不难看出线路是最短的
样例2
输入
4
0 2 0 3
2 0 3 1
0 3 0 4
3 1 4 0
1 0 1 0
0 2
输出
0 1 2
题解
题面描述
给定一个公园中的 个景点(编号从 到 ),以及一个 的距离矩阵,矩阵中第 行第 列的元素表示景点 到景点 的距离,距离为 表示不相邻。还有一行标记哪些景点是公园的出入口(用 表示是, 表示否)。最后给定一个入口景点编号 和一个出口景点编号 。要求在允许经过任意其他景点但不需要访问所有景点的前提下,找到从 到 的最短游园线路,并输出具体经过的景点序列。如果存在多条等长最短路径,则输出按景点序号从小到大(即字典序)最小的那条路径。
思路
-
将景点和距离看作一个无向带权图,节点数为 ,边权由距离矩阵给出。
-
使用 Dijkstra 算法 从起点 计算到每个节点的最短距离 。
-
为了在距离相同的情况下选择 字典序最小 的路径,我们在松弛操作中不仅维护距离,还需要维护到达该节点的路径序列 。
-
松弛时若新的距离更小,直接更新;若新的距离等于当前最优距离,则比较两个路径序列的字典序,选择更小者。
-
最终输出 即为所求路径。
算法步骤
-
初始化:对所有 ,, ;令 , 。
-
使用优先队列(最小堆)存储 ,初始压入 。
-
当队列非空时,取出距离最小的 ,若 则跳过;否则对所有 (满足 )尝试松弛:
- 若 ,则
加上
-
否则若则比较新路径 与旧路径 的字典序,若前者更小,则用前者更新 。
-
若发生更新,则将 压入队列。
- 若 ,则
-
结束后,输出 。
代码实现
Python (C++和JAVA代码见在线OJ网址)
import heapq
def shortest_path(N, g, S, T):
INF = float('inf')
dist = [INF] * N
best = {S: [S]}
dist[S] = 0
pq = [(0, [S], S)] # (distance, path, node)
while pq:
d, path, u = heapq.heappop(pq)
if u == T:
return path
if d > dist[u]:
continue
for v, w in enumerate(g[u]):
if w == 0:
continue
nd = d + w
new_path = path + [v]
# update if strictly shorter, or same length but lexicographically smaller
if nd < dist[v] or (nd == dist[v] and new_path < best.get(v, [])):
dist[v] = nd
best[v] = new_path
heapq.heappush(pq, (nd, new_path, v))
return []
if __name__ == '__main__':
N = int(input())
g = [list(map(int, input().split())) for _ in range(N)]
_ = input() # read & ignore is_entrance line
S, T = map(int, input().split())
print(*shortest_path(N, g, S, T))
第3题-爬山线路规划
题目内容
给定一个二维数组表示一座山的地图,数组中的每个元素代表坐标处 山的高度。登山员从山底出发,爬到山峰。
山底的含义:中高度为0的坐标点。
山峰的含义:中高度最高的坐标点。
山底和山峰有且仅有一个坐标。
登山员每次移动只能从当前位置向上下左右四个方向移动一格,向高处移动时,移动到的位置的山的高度不能高于当前位置山的高度加上固定的攀爬能力值;向低处移动时,移动到的位置的山的高度不能低于当前位置山的高度减去。
数值取值范围:
请计算出从山底移动到山峰,最少需要移动几次?
输入描述
1.第一行为的值
2.第二行为
3.从第三行开始为行列的高度二维数组,每行的高度数字中间用空格分割
输出描述
从山底移动到山峰,最少移动次数。
如果无法移动至山峰,则输出
样例1
输入
2
3 2
1 3
0 4
5 3
输出
5
说明
攀登能力为,行列的山峰坐标,山底的坐标为高度,山峰的坐标为高度。仅有一条路线
初始位置山底高度高度高度高度高度高度
共需要移动次
样例2
输入
1
4 5
1 1 1 1 1
1 0 1 2 1
1 1 1 3 1
1 1 1 1 1
输出
3
说明
攀登能力为,行列的山峰坐标,山底的坐标为,山峰的坐标为。
最短路线为
初始位置山底高度高度高度山峰高度
共需要移动次
样例3
输入
1
4 5
1 1 1 1 1
1 0 1 2 1
1 1 1 9 1
1 1 1 1 1
输出
-1
说明
无法达到山峰,输出
提示
.山底和山峰有且仅有一个坐标。
.初始位置在山底,山底不一定在数组边缘位置,见样例
.山峰的高度大于
题解
题解思路
我们可以将山脉地图看成一个二维网格,每个格子代表一个节点,相邻(上、下、左、右)格子之间有边相连。由于每步移动代价相同(均为 1),并且我们要找从山底到山峰的最少步数,最合适的算法是 广度优先搜索(BFS)。
具体步骤如下:
-
扫描地图,找到山底起点
start
(高度为 0 的唯一坐标)和山峰终点target
(最高高度的唯一坐标)。 -
初始化队列,将
start
入队,并用一个与地图同型的布尔数组vis
记录访问状态。 -
BFS 过程:
-
向高处移动:
grid[nx][ny] - grid[x][y] ≤ climbAbility
-
向低处移动:
grid[x][y] - grid[nx][ny] ≤ climbAbility
-
每次从队列取出当前格子
(x,y)
及其已走步数d
。 -
枚举四个方向的新坐标
(nx,ny)
,判断是否越界、未访问,并且满足高度差限制: -
若满足,则将
(nx,ny)
标记为已访问并入队,步数d+1
。 -
若
(nx,ny)
为target
,即可立即返回d+1
。
-
-
若 BFS 结束仍未到达山峰,返回
-1
。
算法分析
-
正确性:BFS 保证第一次到达终点时所走步数即为最少步数。
-
高度限制判断:无论上坡还是下坡,实际上都可统一为
|grid[nx][ny] - grid[x][y]| ≤ climbAbility
。 -
访问标记:防止重复入队,降低时间开销。
复杂度分析
-
令网格大小为
N=M行×M列
,BFS 最多访问所有格子一次,每个格子检查 4 个方向。 -
时间复杂度:O(N×M)。
-
空间复杂度:O(N×M),用于队列和访问数组。
代码实现
Python
import sys
from collections import deque
def min_steps(g, ability):
n, m = len(g), len(g[0])
start = target = None
max_h = -1
for i in range(n):
for j, h in enumerate(g[i]):
if h == 0: start = (i, j)
if h > max_h:
max_h, target = h, (i, j)
vis = [[False]*m for _ in range(n)]
dq = deque([(start[0], start[1], 0)])
vis[start[0]][start[1]] = True
for x, y, d in dq:
if (x, y) == target: return d
for dx, dy in ((1,0),(-1,0),(0,1),(0,-1)):
nx, ny = x+dx, y+dy
if 0 <= nx < n and 0 <= ny < m and not vis[nx][ny]:
if abs(g[nx][ny] - g[x][y]) <= ability:
vis[nx][ny] = True
dq.append((nx, ny, d+1))
return -1
if __name__ == "__main__":
data = sys.stdin.read().split()
ability = int(data[0])
n, m = map(int, data[1:3])
vals = list(map(int, data[3:]))
grid = [vals[i*m:(i+1)*m] for i in range(n)]
print(min_steps(grid, ability))