python题目练习
以下是各题的Python实现,包含详细注释和实现思路,按题目顺序逐一呈现:
1-1 图的存储-邻接表
实现思路
- 读取节点数N、边数M和有向/无向标志S。
- 初始化邻接表为N个空列表(节点编号从0开始,输入节点编号需减1转换)。
- 对每条边,按表头插入法(逆序插入保证输出唯一)添加到邻接表:
- 有向图仅添加u→v。
- 无向图同时添加u→v和v→u。
- 按格式输出每条邻接表项,元素间空格分隔,末尾带空格。
# 读取输入(处理输入样例中多余的行号,实际提交时直接读取有效行)
import sys
def main():
# 读取第一行:N节点数,M边数,S有向无向标志
line = sys.stdin.readline()
while line.strip() == '': # 跳过空行
line = sys.stdin.readline()
N, M, S = map(int, line.strip().split())
# 初始化邻接表:N个节点,编号0~N-1
adj = [[] for _ in range(N)]
for _ in range(M):
# 读取边的两个节点(输入1~N,转换为0~N-1)
edge_line = sys.stdin.readline()
while edge_line.strip() == '':
edge_line = sys.stdin.readline()
u, v = map(int, edge_line.strip().split())
u -= 1
v -= 1
# 表头插入法:在列表头部添加(保证输出顺序唯一)
adj[u].insert(0, v)
# 无向图需添加反向边
if S == 0:
adj[v].insert(0, u)
# 按格式输出
for i in range(N):
# 转换为字符串,元素间空格分隔,末尾带空格
print(f"{i}:{' '.join(map(str, adj[i]))} ")
if __name__ == "__main__":
main()
1-2 寻宝图
实现思路
- 读取地图尺寸N(行)和M(列)。
- 遍历地图每个格子,用深度优先搜索(DFS)或广度优先搜索(BFS)识别连通块(岛屿):
- 仅处理未访问过的陆地(1)或宝藏(2-9)。
- 搜索过程中标记访问状态,同时判断该岛屿是否包含宝藏。
- 统计岛屿总数和含宝藏的岛屿数,输出结果。
import sys
from collections import deque
def main():
# 读取N和M
line = sys.stdin.readline()
while line.strip() == '':
line = sys.stdin.readline()
N, M = map(int, line.strip().split())
# 读取地图:转为二维列表,0为水域,1为陆地,2-9为宝藏
grid = []
for _ in range(N):
row_line = sys.stdin.readline()
while row_line.strip() == '':
row_line = sys.stdin.readline()
# 去除可能的空格,转为整数列表
row = [int(c) for c in row_line.strip() if c.isdigit()]
grid.append(row)
# 访问标记矩阵:记录是否已处理该格子
visited = [[False for _ in range(M)] for _ in range(N)]
total_islands = 0 # 岛屿总数
treasure_islands = 0 # 含宝藏的岛屿数
# 四个方向(上下左右)
dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for i in range(N):
for j in range(M):
# 未访问且是陆地/宝藏
if not visited[i][j] and grid[i][j] != 0:
total_islands += 1
has_treasure = False
# BFS遍历连通块
q = deque()
q.append((i, j))
visited[i][j] = True
while q:
x, y = q.popleft()
# 判断是否为宝藏
if grid[x][y] >= 2:
has_treasure = True
# 遍历四个方向
for dx, dy in dirs:
nx = x + dx
ny = y + dy
# 边界判断+未访问+非水域
if 0 <= nx < N and 0 <= ny < M and not visited[nx][ny] and grid[nx][ny] != 0:
visited[nx][ny] = True
q.append((nx, ny))
# 该岛屿是否有宝藏
if has_treasure:
treasure_islands += 1
# 输出结果(样例输出格式可能有误,按题目要求输出两个整数)
print(f"{total_islands} {treasure_islands}")
if __name__ == "__main__":
main()
1-3 深入虎穴
实现思路
- 读取门的数量N,构建反向邻接表(找入口:入度为0的门)。
- 从入口出发,用BFS或DFS计算每个门到入口的距离。
- 找出距离最大的门编号,输出结果。
import sys
from collections import deque
def main():
# 读取门的数量N
line = sys.stdin.readline()
while line.strip() == '':
line = sys.stdin.readline()
N = int(line.strip())
# 构建正向邻接表(门->可达的门)和反向邻接表(门->到达该门的门)
forward_adj = [[] for _ in range(N+1)] # 门编号1~N
reverse_adj = [[] for _ in range(N+1)]
for door in range(1, N+1):
door_line = sys.stdin.readline()
while door_line.strip() == '':
door_line = sys.stdin.readline()
parts = list(map(int, door_line.strip().split()))
K = parts[0]
dest_doors = parts[1:] if K > 0 else []
forward_adj[door] = dest_doors
# 构建反向邻接表
for d in dest_doors:
reverse_adj[d].append(door)
# 找入口:入度为0的门(反向邻接表为空)
entrance = -1
for door in range(1, N+1):
if len(reverse_adj[door]) == 0:
entrance = door
break
# BFS计算每个门到入口的距离
distance = [0] * (N+1)
visited = [False] * (N+1)
q = deque()
q.append(entrance)
visited[entrance] = True
while q:
current = q.popleft()
# 遍历所有可达的门
for next_door in forward_adj[current]:
if not visited[next_door]:
visited[next_door] = True
distance[next_door] = distance[current] + 1
q.append(next_door)
# 找距离最大的门(题目保证唯一)
max_dist = -1
result_door = -1
for door in range(1, N+1):
if distance[door] > max_dist:
max_dist = distance[door]
result_door = door
print(result_door)
if __name__ == "__main__":
main()
1-4 繁忙的都市
实现思路
- 题目要求等价于求最小生成树(MST):
- 满足连通所有节点(要求1)。
- 边数最少(MST边数为n-1,要求2)。
- 最大边权最小(MST的性质,要求3)。
- 用Kruskal算法实现:
- 按边权从小到大排序。
- 用并查集(Union-Find)避免环。
- 选够n-1条边后,最大边权即为答案。
import sys
class UnionFind:
"""并查集类:用于判断节点是否连通"""
def __init__(self, size):
self.parent = list(range(size+1)) # 节点1~n
def find(self, x):
"""找根节点,路径压缩"""
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
"""合并两个集合"""
root_x = self.find(x)
root_y = self.find(y)
if root_x != root_y:
self.parent[root_y] = root_x
return True
return False # 已连通,合并失败
def main():
# 读取n(交叉路口数)和m(道路数)
line = sys.stdin.readline()
while line.strip() == '':
line = sys.stdin.readline()
n, m = map(int, line.strip().split())
# 读取所有道路:存储为(权值c, 节点u, 节点v)
roads = []
for _ in range(m):
road_line = sys.stdin.readline()
while road_line.strip() == '':
road_line = sys.stdin.readline()
u, v, c = map(int, road_line.strip().split())
roads.append((c, u, v))
# Kruskal算法:按权值排序
roads.sort()
uf = UnionFind(n)
selected = 0 # 已选边数
max_c = 0 # 选中边的最大权值
for c, u, v in roads:
if uf.union(u, v):
selected += 1
if c > max_c:
max_c = c
# 选够n-1条边,MST完成
if selected == n - 1:
break
# 输出结果:选中边数和最大权值
print(f"{selected} {max_c}")
if __name__ == "__main__":
main()
1-5 哲哲打游戏
实现思路
- 读取剧情点数量N和操作数M。
- 构建剧情点跳转表:存储每个剧情点的可选跳转目标。
- 初始化当前剧情点为1,存档数组(最多100个档位)。
- 处理每个操作:
- 0 j:跳转到当前剧情点的第j个目标(j从1开始)。
- 1 j:存档当前剧情点到档位j,输出该剧情点。
- 2 j:读取档位j的剧情点,更新当前剧情点。
- 最后输出最终剧情点。
import sys
def main():
# 读取N(剧情点)和M(操作数)
line = sys.stdin.readline()
while line.strip() == '':
line = sys.stdin.readline()
N, M = map(int, line.strip().split())
# 构建剧情跳转表:jump[i]是剧情点i的跳转列表(1-based)
jump = [[] for _ in range(N+1)]
for i in range(1, N+1):
jump_line = sys.stdin.readline()
while jump_line.strip() == '':
jump_line = sys.stdin.readline()
parts = list(map(int, jump_line.strip().split()))
K = parts[0]
jump[i] = parts[1:] if K > 0 else []
# 初始化:当前剧情点、存档数组(100个档位,1-based)
current = 1
save = [0] * 101 # save[j]存储档位j的剧情点
for _ in range(M):
op_line = sys.stdin.readline()
while op_line.strip() == '':
op_line = sys.stdin.readline()
op_parts = list(map(int, op_line.strip().split()))
op = op_parts[0]
j = op_parts[1]
if op == 0:
# 0 j:第j个选择(j从1开始,列表索引j-1)
current = jump[current][j-1]
elif op == 1:
# 1 j:存档到档位j,输出当前剧情点
save[j] = current
print(current)
elif op == 2:
# 2 j:读取档位j的存档
current = save[j]
# 输出最终剧情点
print(current)
if __name__ == "__main__":
main()
2-1 太空飞行计划问题
实现思路
- 问题转化为最大权闭合子图问题,用最小割模型求解:
- 源点S连接所有实验,边权为实验收益。
- 所有仪器连接汇点T,边权为仪器成本。
- 实验连接所需仪器,边权为无穷大(不可割)。
- 最大净收益 = 所有实验总收益 - 最小割(S-T最小割)。
- 用Edmonds-Karp算法求最大流(最小割=最大流)。
- 遍历残留网络,找到S可达的节点(选中的实验和仪器)。
import sys
from collections import deque
class Edge:
"""边类:存储目标节点、容量、反向边"""
def __init__(self, to, cap, rev):
self.to = to
self.cap = cap
self.rev = rev
class MaxFlow:
"""最大流类:Edmonds-Karp算法"""
def __init__(self, n):
self.size = n
self.graph = [[] for _ in range(n)]
def add_edge(self, fr, to, cap):
"""添加边:正向边cap,反向边0"""
forward = Edge(to, cap, len(self.graph[to]))
backward = Edge(fr, 0, len(self.graph[fr]))
self.graph[fr].append(forward)
self.graph[to].append(backward)
def bfs_level(self, s, t, level):
"""BFS构建层次图,返回是否可达"""
q = deque()
level[:] = [-1] * self.size
level[s] = 0
q.append(s)
while q:
v = q.popleft()
for edge in self.graph[v]:
if edge.cap > 0 and level[edge.to] == -1:
level[edge.to] = level[v] + 1
q.append(edge.to)
if edge.to == t:
return True
return False
def dfs_flow(self, v, t, upTo, iter_, level):
"""DFS寻找增广路,返回可增广流量"""
if v == t:
return upTo
for i in range(iter_[v], len(self.graph[v])):
edge = self.graph[v][i]
if edge.cap > 0 and level[v] < level[edge.to]:
d = self.dfs_flow(edge.to, t, min(upTo, edge.cap), iter_, level)
if d > 0:
edge.cap -= d
self.graph[edge.to][edge.rev].cap += d
return d
iter_[v] += 1
return 0
def max_flow(self, s, t):
"""计算s到t的最大流"""
flow = 0
level = [-1] * self.size
while self.bfs_level(s, t, level):
iter_ = [0] * self.size
while True:
f = self.dfs_flow(s, t, float('inf'), iter_, level)
if f == 0:
break
flow += f
level = [-1] * self.size
return flow
def main():
# 读取m(实验数)和n(仪器数)
line = sys.stdin.readline()
while line.strip() == '':
line = sys.stdin.readline()
m, n = map(int, line.strip().split())
# 节点定义:S=0,实验1~m,仪器m+1~m+n,T=m+n+1
S = 0
T = m + n + 1
mf = MaxFlow(T + 1)
total_profit = 0 # 所有实验总收益
# 读取实验数据
experiments = []
for i in range(1, m+1):
exp_line = sys.stdin.readline()
while exp_line.strip() == '':
exp_line = sys.stdin.readline()
parts = list(map(int, exp_line.strip().split()))
profit = parts[0]
instruments = parts[1:] if len(parts) > 1 else []
experiments.append((profit, instruments))
total_profit += profit
# S连接实验i,边权=收益
mf.add_edge(S, i, profit)
# 实验i连接所需仪器(仪器编号转为m+编号)
for instr in instruments:
instr_node = m + instr
mf.add_edge(i, instr_node, float('inf')) # 无穷大边
# 读取仪器成本
cost_line = sys.stdin.readline()
while cost_line.strip() == '':
cost_line = sys.stdin.readline()
costs = list(map(int, cost_line.strip().split()))
for instr in range(1, n+1):
instr_node = m + instr
cost = costs[instr-1]
# 仪器连接T,边权=成本
mf.add_edge(instr_node, T, cost)
# 计算最大流(最小割)
min_cut = mf.max_flow(S, T)
max_net = total_profit - min_cut # 最大净收益
# 找选中的实验(S可达的实验节点)
selected_exp = []
visited = [False] * (T + 1)
q = deque()
q.append(S)
visited[S] = True
while q:
v = q.popleft()
for edge in mf.graph[v]:
if edge.cap > 0 and not visited[edge.to]:
visited[edge.to] = True
q.append(edge.to)
# 收集实验节点(1~m)
for i in range(1, m+1):
if visited[i]:
selected_exp.append(str(i))
# 找选中的仪器(S可达的仪器节点)
selected_instr = []
for instr in range(1, n+1):
instr_node = m + instr
if visited[instr_node]:
selected_instr.append(str(instr))
# 输出结果
print(' '.join(selected_exp) if selected_exp else '')
print(' '.join(selected_instr) if selected_instr else '')
print(max_net)
if __name__ == "__main__":
main()
2-4 最大流
实现思路
- 用Edmonds-Karp算法(BFS找增广路)求解有向图最大流。
- 构建残留网络,每次BFS寻找从s到t的增广路,记录路径和最小残留容量。
- 沿增广路更新残留网络,重复直到无增广路,总流量即为最大流。
import sys
from collections import deque
class Edge:
def __init__(self, to, cap, rev):
self.to = to
self.cap = cap
self.rev = rev
class MaxFlow:
def __init__(self, n):
self.size = n
self.graph = [[] for _ in range(n)]
def add_edge(self, fr, to, cap):
"""添加有向边fr->to,容量cap"""
forward = Edge(to, cap, len(self.graph[to]))
backward = Edge(fr, 0, len(self.graph[fr]))
self.graph[fr].append(forward)
self.graph[to].append(backward)
def max_flow(self, s, t):
flow = 0
while True:
# BFS找增广路,记录前驱节点和对应边
prev = [-1] * self.size
prev_edge = [-1] * self.size # 记录到达该节点的边在graph[prev[v]]中的索引
q = deque()
q.append(s)
prev[s] = s
while q and prev[t] == -1:
v = q.popleft()
for i, edge in enumerate(self.graph[v]):
if edge.cap > 0 and prev[edge.to] == -1:
prev[edge.to] = v
prev_edge[edge.to] = i
q.append(edge.to)
if edge.to == t:
break
# 无增广路,结束
if prev[t] == -1:
return flow
# 计算增广路的最小残留容量
min_cap = float('inf')
v = t
while v != s:
u = prev[v]
edge = self.graph[u][prev_edge[v]]
min_cap = min(min_cap, edge.cap)
v = u
# 更新残留网络
v = t
while v != s:
u = prev[v]
edge = self.graph[u][prev_edge[v]]
edge.cap -= min_cap
# 更新反向边
self.graph[v][edge.rev].cap += min_cap
v = u
flow += min_cap
def main():
# 读取n(节点数)、m(边数)、s(源点)、t(汇点)
line = sys.stdin.readline()
while line.strip() == '':
line = sys.stdin.readline()
n, m, s, t = map(int, line.strip().split())
# 注意:题目中节点编号可能从1开始,直接使用即可
mf = MaxFlow(n + 1) # 节点1~n
for _ in range(m):
edge_line = sys.stdin.readline()
while edge_line.strip() == '':
edge_line = sys.stdin.readline()
u, v, c = map(int, edge_line.strip().split())
mf.add_edge(u, v, c)
# 计算最大流
print(mf.max_flow(s, t))
if __name__ == "__main__":
main()
2-5 大禹治水
实现思路
- 问题本质是求从源点(家乡,交叉点1)到汇点(大海,交叉点M)的最大流。
- 直接复用最大流模板(Edmonds-Karp算法),注意输入中N是水沟数,M是交叉点数。
import sys
from collections import deque
class Edge:
def __init__(self, to, cap, rev):
self.to = to
self.cap = cap
self.rev = rev
class MaxFlow:
def __init__(self, n):
self.size = n
self.graph = [[] for _ in range(n)]
def add_edge(self, fr, to, cap):
forward = Edge(to, cap, len(self.graph[to]))
backward = Edge(fr, 0, len(self.graph[fr]))
self.graph[fr].append(forward)
self.graph[to].append(backward)
def max_flow(self, s, t):
flow = 0
while True:
prev = [-1] * self.size
prev_edge = [-1] * self.size
q = deque()
q.append(s)
prev[s] = s
while q and prev[t] == -1:
v = q.popleft()
for i, edge in enumerate(self.graph[v]):
if edge.cap > 0 and prev[edge.to] == -1:
prev[edge.to] = v
prev_edge[edge.to] = i
q.append(edge.to)
if prev[t] == -1:
return flow
min_cap = float('inf')
v = t
while v != s:
u = prev[v]
edge = self.graph[u][prev_edge[v]]
min_cap = min(min_cap, edge.cap)
v = u
v = t
while v != s:
u = prev[v]
edge = self.graph[u][prev_edge[v]]
edge.cap -= min_cap
self.graph[v][edge.rev].cap += min_cap
v = u
flow += min_cap
def main():
# 读取N(水沟数)和M(交叉点数)
line = sys.stdin.readline()
while line.strip() == '':
line = sys.stdin.readline()
N, M = map(int, line.strip().split())
# 源点s=1,汇点t=M
mf = MaxFlow(M + 1) # 交叉点1~M
for _ in range(N):
水沟_line = sys.stdin.readline()
while水沟_line.strip() == '':
水沟_line = sys.stdin.readline()
s, e, c = map(int, 水沟_line.strip().split())
mf.add_edge(s, e, c)
print(mf.max_flow(1, M))
if __name__ == "__main__":
main()

pta练习题
浙公网安备 33010602011771号