一、冗余的边II(Kamacoder 109)


python
from collections import defaultdict
# 并查集 - 查找根节点(路径压缩)
def find(fa, x):
if fa[x] != x:
fa[x] = find(fa, fa[x])
return fa[x]
# 并查集 - 合并两个集合,返回是否合并成功
def union(fa, x, y):
fx, fy = find(fa, x), find(fa, y)
if fx != fy:
fa[fx] = fy
return True # 合并成功,无环
return False # 已在一个集合中,成环
# 判断删除某一条边后,是否构成有向树(无环)
def is_tree_after_removal(edges, remove_idx, n):
fa = list(range(n + 1)) # 初始化并查集
for i, (u, v) in enumerate(edges):
if i == remove_idx: # 跳过指定要删除的边
continue
if not union(fa, u, v): # 出现环
return False
return True
# 主函数:找出多余的那条边
def find_redundant_directed_connection(n, edges):
in_deg = defaultdict(list) # 记录每个节点的入边索引(用于找入度为2的点)
for i, (u, v) in enumerate(edges):
in_deg[v].append(i)
# 情况 1:存在某个点入度为 2(有两个父节点)
for v in in_deg:
if len(in_deg[v]) == 2:
i1, i2 = in_deg[v]
# 优先删除后添加的边(尝试是否能变成树)
if is_tree_after_removal(edges, i2, n):
return edges[i2]
# 否则只能删另一条
return edges[i1]
# 情况 2:无入度为2的点,仅存在有向环
fa = list(range(n + 1)) # 初始化并查集
for u, v in edges:
if not union(fa, u, v): # 出现环
return [u, v]
# 读取输入和输出结果
if __name__ == "__main__":
n = int(input()) # 节点数
edges = [list(map(int, input().split())) for _ in range(n)] # 读取边
print(*find_redundant_directed_connection(n, edges)) # 输出多余的那条边
二、寻宝(Kamacoder 53)


最小生成树:在无向图中求一棵树(n-1条边,无环,连通所有点),而且这棵树的边权和最小。
等价于:怎么花最小成本连通无向图的所有点???
Prim(普里姆算法):更适合稠密图,与边的数量没关系,加点法
Kruskal(克鲁斯卡尔算法):更适合稀疏图,与节点的数量没关系,加边法
最小生成树可能不唯一,但是边权和唯一!!!!
python
# 接收输入:v 个顶点,e 条边
v, e = list(map(int, input().strip().split()))
# 使用邻接矩阵初始化图:默认所有节点之间不可达,设为一个很大的数(10001)
graph = [[10001] * (v + 1) for _ in range(v + 1)]
for _ in range(e):
x, y, w = list(map(int, input().strip().split()))
graph[x][y] = w
graph[y][x] = w # 因为是无向图,双向赋值
# 初始化 Prim 算法所需变量
visited = [False] * (v + 1) # 标记哪些节点已加入生成树
minDist = [10001] * (v + 1) # 记录每个节点与当前生成树的最短连接边权重
# 从第一个节点开始构建 MST(通常默认从顶点1开始)
minDist[1] = 0 # 起点到自己权重为 0
# Prim 主循环:重复选择 v 次,每次选择一个未加入树的、距离最小的节点
for _ in range(1, v + 1):
min_val = 10002 # 当前最小权重(初始为无穷大)
cur = -1 # 当前选择加入树的节点
# 遍历所有未访问的节点,选出与树连接距离最小的那个
for j in range(1, v + 1):
if not visited[j] and minDist[j] < min_val:
cur = j
min_val = minDist[j]
visited[cur] = True # 标记该节点已加入 MST
# 更新其他未加入节点的 minDist,如果通过新加入的 cur 更近
for j in range(1, v + 1):
if not visited[j] and graph[cur][j] < minDist[j]:
minDist[j] = graph[cur][j]
# 累加所有边权(minDist[1] 是起点设为 0,可以忽略)
ans = sum(minDist[2:]) # minDist[1] 为起点设为 0,不计入答案
print(ans)
三、软件构建(Kamacoder 117)


拓扑排序:所有点按照先后顺序排成序列,每次选入度为0的点,然后删除这个点和它的出边,如果拓扑排序进行不下去了,说明图中有环。
python
from collections import deque, defaultdict
def topo_sort(n, edges):
indeg = [0] * n # indeg[i] 表示点 i 的入度(被多少个点指向)
graph = defaultdict(list) # 用邻接表 graph 存储有向图,graph[u] 里是所有从 u 出发可达的点
# 构建图和入度数组
for u, v in edges:
graph[u].append(v) # u 指向 v
indeg[v] += 1 # v 的入度加一
# 将所有入度为 0 的点入队,表示它们可以首先被处理
q = deque(i for i in range(n) if indeg[i] == 0)
res = [] # 存储拓扑排序的结果
while q:
u = q.popleft() # 取出一个入度为 0 的点
res.append(u) # 加入结果中
for v in graph[u]: # 遍历 u 指向的所有节点
indeg[v] -= 1 # u 被处理了,v 的入度减一
if indeg[v] == 0: # 若 v 入度减为 0,加入队列等待处理
q.append(v)
# 若结果中包含了所有 n 个节点,说明成功排序;否则图中有环,无法拓扑排序
print(" ".join(map(str, res)) if len(res) == n else -1)
if __name__ == "__main__":
# 读取顶点数 n 和边数 m
n, m = map(int, input().split())
# 读取 m 条边,构建边列表
edges = [tuple(map(int, input().split())) for _ in range(m)]
# 调用拓扑排序函数
topo_sort(n, edges)