python题目练习

以下是各题的Python实现,包含详细注释和实现思路,按题目顺序逐一呈现:

1-1 图的存储-邻接表

实现思路

  1. 读取节点数N、边数M和有向/无向标志S。
  2. 初始化邻接表为N个空列表(节点编号从0开始,输入节点编号需减1转换)。
  3. 对每条边,按表头插入法(逆序插入保证输出唯一)添加到邻接表:
    • 有向图仅添加u→v。
    • 无向图同时添加u→v和v→u。
  4. 按格式输出每条邻接表项,元素间空格分隔,末尾带空格。
# 读取输入(处理输入样例中多余的行号,实际提交时直接读取有效行)
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 寻宝图

实现思路

  1. 读取地图尺寸N(行)和M(列)。
  2. 遍历地图每个格子,用深度优先搜索(DFS)或广度优先搜索(BFS)识别连通块(岛屿):
    • 仅处理未访问过的陆地(1)或宝藏(2-9)。
    • 搜索过程中标记访问状态,同时判断该岛屿是否包含宝藏。
  3. 统计岛屿总数和含宝藏的岛屿数,输出结果。
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 深入虎穴

实现思路

  1. 读取门的数量N,构建反向邻接表(找入口:入度为0的门)。
  2. 从入口出发,用BFS或DFS计算每个门到入口的距离。
  3. 找出距离最大的门编号,输出结果。
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 繁忙的都市

实现思路

  1. 题目要求等价于求最小生成树(MST)
    • 满足连通所有节点(要求1)。
    • 边数最少(MST边数为n-1,要求2)。
    • 最大边权最小(MST的性质,要求3)。
  2. 用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 哲哲打游戏

实现思路

  1. 读取剧情点数量N和操作数M。
  2. 构建剧情点跳转表:存储每个剧情点的可选跳转目标。
  3. 初始化当前剧情点为1,存档数组(最多100个档位)。
  4. 处理每个操作:
    • 0 j:跳转到当前剧情点的第j个目标(j从1开始)。
    • 1 j:存档当前剧情点到档位j,输出该剧情点。
    • 2 j:读取档位j的剧情点,更新当前剧情点。
  5. 最后输出最终剧情点。
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 太空飞行计划问题

实现思路

  1. 问题转化为最大权闭合子图问题,用最小割模型求解:
    • 源点S连接所有实验,边权为实验收益。
    • 所有仪器连接汇点T,边权为仪器成本。
    • 实验连接所需仪器,边权为无穷大(不可割)。
  2. 最大净收益 = 所有实验总收益 - 最小割(S-T最小割)。
  3. 用Edmonds-Karp算法求最大流(最小割=最大流)。
  4. 遍历残留网络,找到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 最大流

实现思路

  1. 用Edmonds-Karp算法(BFS找增广路)求解有向图最大流。
  2. 构建残留网络,每次BFS寻找从s到t的增广路,记录路径和最小残留容量。
  3. 沿增广路更新残留网络,重复直到无增广路,总流量即为最大流。
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. 问题本质是求从源点(家乡,交叉点1)到汇点(大海,交叉点M)的最大流。
  2. 直接复用最大流模板(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()
posted @ 2025-12-12 09:20  小西贝の博客  阅读(13)  评论(1)    收藏  举报