目录
Dijkstra、Floyd、Bellman ford、SPFA算法
POJ1797、POJ1860、POJ3259、POJ3268
POJ1797 - Heavy Transportation
POJ1251、POJ1287、POJ2031、POJ2421
POJ2367、POJ1094、POJ3687、POJ1270
SDUTOJ2498、HDU4019、POJ1949、HDU1224、HDU1317
SDUTOJ2498 - Prim's MST Algorithm
一、最短路径
Dijkstra、Floyd、Bellman ford、SPFA算法
Dijkstra算法
特点:
- 适用于没有负权边的图。
- 时间复杂度:使用优先队列时为 O((V+E)logV)O((V + E) \log V)O((V+E)logV),其中 V 是顶点数,E 是边数。
适用场景:
- 单源最短路径。
- 常用于路由算法和导航系统。
步骤:
- 初始化源点的距离为0,其他点的距离为无穷大。
- 使用优先队列选择当前距离最小的点。
- 更新该点的邻居的距离。
- 重复步骤2和3直到所有点都被处理。
Floyd算法
特点:
- 适用于任意带权图,包括负权边。
- 时间复杂度: O(V3)O(V^3)O(V3)。
适用场景:
- 求解所有顶点对的最短路径。
- 常用于网络分析、交通网络等。
步骤:
- 初始化距离矩阵,若有直接边则为边的权重,否则为无穷大,自己到自己为0。
- 对每对顶点 (i, j),考虑所有中间点 k,更新 i 到 j 的最短距离。
Bellman-Ford算法
特点:
- 适用于有负权边的图。
- 时间复杂度: O(VE)O(VE)O(VE)。
适用场景:
- 单源最短路径。
- 可以检测负权环。
步骤:
- 初始化源点的距离为0,其他点的距离为无穷大。
- 对所有边进行 V-1 次松弛操作。
- 检测负权环,如果在第 V 次松弛操作中还有距离更新,则存在负权环。
SPFA算法
特点:
- Bellman-Ford算法的优化版本。
- 平均情况下比 Bellman-Ford 更快,时间复杂度为 O(kE)O(kE)O(kE),k 通常为 2~3。
适用场景:
- 单源最短路径,尤其在稀疏图和有负权边时。
步骤:
- 初始化源点的距离为0,其他点的距离为无穷大,并将源点入队。
- 处理队列中的点,松弛其所有邻居的距离,并将更新后的邻居入队。
- 重复直到队列为空。
POJ1797、POJ1860、POJ3259、POJ3268
POJ1797 - Heavy Transportation
描述:
- 给定一个带权无向图,找到从源点到目标点的最大最小边权,即从源到目标路径中最小的边权最大。
推荐算法:
- Dijkstra's Algorithm 的变种(最大最小路径)。
- 使用最大堆(优先队列)来存储边权。
- 每次选择最大权边,并更新路径上的最小边权。
Python代码示例
import heapq def dijkstra_max_min(graph, start, end): max_heap = [(-float('inf'), start)] # (最大边权,节点) min_edge = {start: float('inf')} while max_heap: min_val, u = heapq.heappop(max_heap) min_val = -min_val if u == end: return min_val for v, weight in graph[u]: next_val = min(min_val, weight) if v not in min_edge or next_val > min_edge[v]: min_edge[v] = next_val heapq.heappush(max_heap, (-next_val, v)) return -1 # 读取输入并构建图
POJ1860 - Currency Exchange
描述:
- 货币兑换问题,判断是否存在一种方式可以通过一系列的货币兑换使得初始货币增值。
推荐算法:
- Bellman-Ford Algorithm(检测负环)。
- 将每种兑换率转换为图中的边权(使用对数换算:
log(rate)
),从而将乘法转换为加法。 - 检测是否存在负环,负环表示可以通过兑换获得更多的初始货币。
- 将每种兑换率转换为图中的边权(使用对数换算:
Python代码示例
import math def bellman_ford_currency_exchange(graph, start, n): dist = [-float('inf')] * n dist[start] = 0 for _ in range(n - 1): for u in range(n): for v, rate in graph[u]: if dist[u] + math.log(rate) > dist[v]: dist[v] = dist[u] + math.log(rate) for u in range(n): for v, rate in graph[u]: if dist[u] + math.log(rate) > dist[v]: return True return False # 读取输入并构建图
POJ3259 - Wormholes
描述:
- 给定一个图,其中包括一些负权边,判断是否存在负权环。
推荐算法:
- Bellman-Ford Algorithm(检测负环)。
- 执行 V-1 次松弛操作后,再进行一次松弛操作,如果仍有边可以松弛,则存在负权环。
Python代码示例
def bellman_ford_negative_cycle(graph, n): dist = [float('inf')] * n dist[0] = 0 for _ in range(n - 1): for u in range(n): for v, weight in graph[u]: if dist[u] + weight < dist[v]: dist[v] = dist[u] + weight for u in range(n): for v, weight in graph[u]: if dist[u] + weight < dist[v]: return True return False # 读取输入并构建图
POJ3268 - Silver Cow Party
描述:
- 一个农场的奶牛要去参加聚会,要求计算每头奶牛从家到聚会地点再返回家的最短路径。
推荐算法:
- Dijkstra's Algorithm(单源最短路径)。
- 分别计算每个奶牛从家到聚会地点的最短路径和从聚会地点回家的最短路径。
- 将两者相加得到每头奶牛的总路径长度。
Python代码示例
import heapq def dijkstra(graph, start, n): dist = [float('inf')] * n dist[start] = 0 priority_queue = [(0, start)] while priority_queue: current_dist, u = heapq.heappop(priority_queue) if current_dist > dist[u]: continue for v, weight in graph[u]: distance = current_dist + weight if distance < dist[v]: dist[v] = distance heapq.heappush(priority_queue, (distance, v)) return dist # 读取输入并构建图
二、最小生成树
Prim、Kruskal算法
Prim和Kruskal算法都是经典的最小生成树(MST)算法,用于在一个无向加权图中找到一棵包含所有顶点且总权重最小的树。
Prim算法
特点:
- 从一个起始顶点开始构建最小生成树。
- 使用优先队列(最小堆)来选择当前最小权重边。
- 时间复杂度: O((V+E)logV)O((V + E) \log V)O((V+E)logV),适用于稠密图。
步骤:
- 初始化:选择一个起点,并将该点加入MST集合。
- 使用最小堆将与MST集合相连的边按权重排序。
- 选择权重最小的边,并将该边连接的另一个顶点加入MST集合。
- 更新最小堆,包含新的顶点所连接的边。
- 重复步骤2-4,直到所有顶点都被包含在MST集合中。
示例代码:
import heapq
def prim(graph, start):
mst = []
visited = set([start])
edges = [(weight, start, to) for to, weight in graph[start]]
heapq.heapify(edges)
while edges:
weight, frm, to = heapq.heappop(edges)
if to not in visited:
visited.add(to)
mst.append((frm, to, weight))
for next_to, next_weight in graph[to]:
if next_to not in visited:
heapq.heappush(edges, (next_weight, to, next_to))
return mst
# 示例图的表示方法
# graph = {0: [(1, 4), (2, 1)], 1: [(0, 4), (2, 3), (3, 2)], 2: [(0, 1), (1, 3), (3, 4)], 3: [(1, 2), (2, 4)]}
Kruskal算法
特点:
- 从边的角度出发,按权重从小到大排序,然后逐一选择边。
- 使用并查集(Union-Find)来检测是否形成环。
- 时间复杂度: O(ElogE+Eα(V))O(E \log E + E \alpha(V))O(ElogE+Eα(V)),适用于稀疏图。
步骤:
- 初始化:将所有边按权重从小到大排序。
- 使用并查集初始化每个顶点所属的集合。
- 依次选择权重最小的边,如果边的两个顶点属于不同集合,则将该边加入MST,并合并两个集合。
- 重复步骤3,直到MST包含 V−1V-1V−1 条边。
示例代码:
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, u):
if self.parent[u] != u:
self.parent[u] = self.find(self.parent[u])
return self.parent[u]
def union(self, u, v):
root_u = self.find(u)
root_v = self.find(v)
if root_u != root_v:
if self.rank[root_u] > self.rank[root_v]:
self.parent[root_v] = root_u
elif self.rank[root_u] < self.rank[root_v]:
self.parent[root_u] = root_v
else:
self.parent[root_v] = root_u
self.rank[root_u] += 1
def kruskal(graph, n):
edges = [(weight, u, v) for u in range(n) for v, weight in graph[u]]
edges.sort()
uf = UnionFind(n)
mst = []
for weight, u, v in edges:
if uf.find(u) != uf.find(v):
uf.union(u, v)
mst.append((u, v, weight))
return mst
# 示例图的表示方法
# graph = {0: [(1, 4), (2, 1)], 1: [(0, 4), (2, 3), (3, 2)], 2: [(0, 1), (1, 3), (3, 4)], 3: [(1, 2), (2, 4)]}
Prim和Kruskal算法的对比
-
Prim算法:
- 更适用于稠密图,因为其时间复杂度与边的数量关系密切。
- 需要访问已在MST中的顶点及其邻接边。
- 边的选择是局部的,逐步扩展MST。
-
Kruskal算法:
- 更适用于稀疏图,因为其时间复杂度主要取决于边的数量。
- 需要对所有边进行全局排序。
- 边的选择是全局的,每次选择权重最小且不形成环的边。
POJ1251、POJ1287、POJ2031、POJ2421
POJ1251 - Jungle Roads
描述:
- 给定一个带权无向图,表示一个村庄的路径,要求找到覆盖所有村庄的最小路径总和,即最小生成树(MST)。
推荐算法:
- Kruskal's Algorithm 或 Prim's Algorithm。
示例代码:
使用 Kruskal's Algorithm:
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, u):
if self.parent[u] != u:
self.parent[u] = self.find(self.parent[u])
return self.parent[u]
def union(self, u, v):
root_u = self.find(u)
root_v = self.find(v)
if root_u != root_v:
if self.rank[root_u] > self.rank[root_v]:
self.parent[root_v] = root_u
elif self.rank[root_u] < self.rank[root_v]:
self.parent[root_u] = root_v
else:
self.parent[root_v] = root_u
self.rank[root_u] += 1
def kruskal(n, edges):
edges.sort(key=lambda x: x[2])
uf = UnionFind(n)
mst_weight = 0
mst_edges = []
for u, v, weight in edges:
if uf.find(u) != uf.find(v):
uf.union(u, v)
mst_weight += weight
mst_edges.append((u, v, weight))
return mst_weight
# 读取输入并构建图
# edges = [(u, v, weight), ...]
# n = number of nodes
# result = kruskal(n, edges)
POJ1287 - Networking
描述:
- 给定一个带权无向图,表示网络连接,要求找到最小生成树(MST)。
推荐算法:
- Kruskal's Algorithm 或 Prim's Algorithm。
示例代码:
使用 Prim's Algorithm:
import heapq
def prim(graph, start):
mst_weight = 0
visited = set([start])
edges = [(weight, start, to) for to, weight in graph[start]]
heapq.heapify(edges)
while edges:
weight, frm, to = heapq.heappop(edges)
if to not in visited:
visited.add(to)
mst_weight += weight
for next_to, next_weight in graph[to]:
if next_to not in visited:
heapq.heappush(edges, (next_weight, to, next_to))
return mst_weight
# 读取输入并构建图
# graph = {0: [(1, 4), (2, 1)], 1: [(0, 4), (2, 3), (3, 2)], 2: [(0, 1), (1, 3), (3, 4)], 3: [(1, 2), (2, 4)]}
# result = prim(graph, start_node)
POJ2031 - Sightseeing tour
描述:
- 给定一个带权无向图,表示景点之间的路径,要求找到一条从某个景点出发,经过所有景点再回到原点的最小路径,即旅行商问题(TSP)。
推荐算法:
- 动态规划 或 近似算法(如遗传算法、模拟退火等)。
示例代码:
使用 动态规划(Held-Karp算法):
def tsp(graph):
n = len(graph)
dp = [[float('inf')] * n for _ in range(1 << n)]
dp[1][0] = 0
for mask in range(1 << n):
for u in range(n):
if mask & (1 << u):
for v in range(n):
if not mask & (1 << v):
dp[mask | (1 << v)][v] = min(dp[mask | (1 << v)][v], dp[mask][u] + graph[u][v])
return min(dp[(1 << n) - 1][v] + graph[v][0] for v in range(1, n))
# 读取输入并构建图
# graph = [[0, 29, 20, 21], [29, 0, 15, 17], [20, 15, 0, 28], [21, 17, 28, 0]]
# result = tsp(graph)
POJ2421 - Constructing Roads
描述:
- 给定一个带权无向图,要求找到最小生成树(MST)。
推荐算法:
- Kruskal's Algorithm 或 Prim's Algorithm。
示例代码:
使用 Kruskal's Algorithm:
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, u):
if self.parent[u] != u:
self.parent[u] = self.find(self.parent[u])
return self.parent[u]
def union(self, u, v):
root_u = self.find(u)
root_v = self.find(v)
if root_u != root_v:
if self.rank[root_u] > self.rank[root_v]:
self.parent[root_v] = root_u
elif self.rank[root_u] < self.rank[root_v]:
self.parent[root_u] = root_v
else:
self.parent[root_v] = root_u
self.rank[root_u] += 1
def kruskal(n, edges):
edges.sort(key=lambda x: x[2])
uf = UnionFind(n)
mst_weight = 0
mst_edges = []
for u, v, weight in edges:
if uf.find(u) != uf.find(v):
uf.union(u, v)
mst_weight += weight
mst_edges.append((u, v, weight))
return mst_weight
# 读取输入并构建图
# edges = [(u, v, weight), ...]
# n = number of nodes
# result = kruskal(n, edges)
三、拓扑排序
POJ2367、POJ1094、POJ3687、POJ1270
POJ2367 - Network
描述:
- 给定一个有向图,要求找到图中所有的强连通分量(SCC)。
推荐算法:
- Tarjan's Algorithm 或 Kosaraju's Algorithm。
示例代码:
使用 Tarjan's Algorithm:
def tarjan_scc(graph):
n = len(graph)
index = 0
stack = []
indices = [-1] * n
lowlinks = [-1] * n
on_stack = [False] * n
sccs = []
def strongconnect(v):
nonlocal index
indices[v] = index
lowlinks[v] = index
index += 1
stack.append(v)
on_stack[v] = True
for w in graph[v]:
if indices[w] == -1:
strongconnect(w)
lowlinks[v] = min(lowlinks[v], lowlinks[w])
elif on_stack[w]:
lowlinks[v] = min(lowlinks[v], indices[w])
if lowlinks[v] == indices[v]:
scc = []
while True:
w = stack.pop()
on_stack[w] = False
scc.append(w)
if w == v:
break
sccs.append(scc)
for v in range(n):
if indices[v] == -1:
strongconnect(v)
return sccs
# 示例图的表示方法
# graph = {0: [1], 1: [2], 2: [0, 3], 3: [4], 4: [5], 5: [3]}
# result = tarjan_scc(graph)
POJ1094 - Sorting It All Out
描述:
- 给定一组不等式关系,要求判断是否存在唯一的拓扑排序,或者检测是否存在矛盾。
推荐算法:
- 拓扑排序 结合 Kahn's Algorithm。
示例代码:
from collections import deque, defaultdict
def topological_sort(n, edges):
in_degree = [0] * n
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
in_degree[v] += 1
queue = deque([i for i in range(n) if in_degree[i] == 0])
topo_order = []
unique = True
while queue:
if len(queue) > 1:
unique = False
node = queue.popleft()
topo_order.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
if len(topo_order) != n:
return "Inconsistency found."
elif not unique:
return "Sorted sequence cannot be determined."
else:
return " ".join(map(str, topo_order))
# 示例输入
# n = 4
# edges = [(0, 1), (1, 2), (3, 1)]
# result = topological_sort(n, edges)
POJ3687 - Placing Lampposts
描述:
- 给定一个网格,每个点要么是空地要么是建筑,要求放置尽量少的路灯照亮所有空地。
推荐算法:
- 二分图最大匹配,即 Hopcroft-Karp Algorithm。
示例代码:
class BipartiteMatcher:
def __init__(self, n, m):
self.n = n
self.m = m
self.edges = [[] for _ in range(n)]
self.left_match = [-1] * n
self.right_match = [-1] * m
self.dist = [-1] * n
def add_edge(self, u, v):
self.edges[u].append(v)
def bfs(self):
queue = []
for i in range(self.n):
if self.left_match[i] == -1:
self.dist[i] = 0
queue.append(i)
else:
self.dist[i] = float('inf')
found_augmenting_path = False
for u in queue:
if self.dist[u] >= 0:
for v in self.edges[u]:
if self.right_match[v] == -1:
found_augmenting_path = True
elif self.dist[self.right_match[v]] == float('inf'):
self.dist[self.right_match[v]] = self.dist[u] + 1
queue.append(self.right_match[v])
return found_augmenting_path
def dfs(self, u):
if u != -1:
for v in self.edges[u]:
if self.right_match[v] == -1 or (self.dist[self.right_match[v]] == self.dist[u] + 1 and self.dfs(self.right_match[v])):
self.left_match[u] = v
self.right_match[v] = u
return True
self.dist[u] = float('inf')
return False
return True
def maximum_matching(self):
matching = 0
while self.bfs():
for i in range(self.n):
if self.left_match[i] == -1:
if self.dfs(i):
matching += 1
return matching
# 示例使用
# n, m = number of vertices on each side of bipartite graph
# matcher = BipartiteMatcher(n, m)
# matcher.add_edge(u, v) # 添加二分图中的边
# result = matcher.maximum_matching()
POJ1270 - Factorial Remainder
描述:
- 给定一个大整数N和一个小整数M,求N!对M取模的结果。
推荐算法:
- 数论,利用模运算性质和中国剩余定理(CRT)。
示例代码:
def factorial_mod(n, m):
if n >= m:
return 0
result = 1
for i in range(2, n + 1):
result = (result * i) % m
return result
# 示例输入
# n = 10
# m = 7
# result = factorial_mod(n, m)
四、关键路径
SDUTOJ2498、HDU4019、POJ1949、HDU1224、HDU1317
SDUTOJ2498 - Prim's MST Algorithm
描述:
- 该题需要你实现Prim算法来找到最小生成树(MST)。
推荐算法:
- Prim's Algorithm。
示例代码:
import heapq
def prim(graph, start):
mst_weight = 0
visited = set([start])
edges = [(weight, start, to) for to, weight in graph[start]]
heapq.heapify(edges)
while edges:
weight, frm, to = heapq.heappop(edges)
if to not in visited:
visited.add(to)
mst_weight += weight
for next_to, next_weight in graph[to]:
if next_to not in visited:
heapq.heappush(edges, (next_weight, to, next_to))
return mst_weight
# 示例输入
# graph = {0: [(1, 4), (2, 1)], 1: [(0, 4), (2, 3), (3, 2)], 2: [(0, 1), (1, 3), (3, 4)], 3: [(1, 2), (2, 4)]}
# start_node = 0
# result = prim(graph, start_node)
HDU4019 - Color the Blocks
描述:
- 给定一个由黑白方块组成的矩阵,要求找到最小覆盖顶点集。
推荐算法:
- 二分图最大匹配,即 Hopcroft-Karp Algorithm。
示例代码:
class BipartiteMatcher:
def __init__(self, n, m):
self.n = n
self.m = m
self.edges = [[] for _ in range(n)]
self.left_match = [-1] * n
self.right_match = [-1] * m
self.dist = [-1] * n
def add_edge(self, u, v):
self.edges[u].append(v)
def bfs(self):
queue = []
for i in range(self.n):
if self.left_match[i] == -1:
self.dist[i] = 0
queue.append(i)
else:
self.dist[i] = float('inf')
found_augmenting_path = False
for u in queue:
if self.dist[u] >= 0:
for v in self.edges[u]:
if self.right_match[v] == -1:
found_augmenting_path = True
elif self.dist[self.right_match[v]] == float('inf'):
self.dist[self.right_match[v]] = self.dist[u] + 1
queue.append(self.right_match[v])
return found_augmenting_path
def dfs(self, u):
if u != -1:
for v in self.edges[u]:
if self.right_match[v] == -1 or (self.dist[self.right_match[v]] == self.dist[u] + 1 and self.dfs(self.right_match[v])):
self.left_match[u] = v
self.right_match[v] = u
return True
self.dist[u] = float('inf')
return False
return True
def maximum_matching(self):
matching = 0
while self.bfs():
for i in range(self.n):
if self.left_match[i] == -1:
if self.dfs(i):
matching += 1
return matching
# 示例使用
# n, m = number of vertices on each side of bipartite graph
# matcher = BipartiteMatcher(n, m)
# matcher.add_edge(u, v) # 添加二分图中的边
# result = matcher.maximum_matching()
POJ1949 - Convex Hull Trick
描述:
- 给定一组线段,要求最大化y值。
推荐算法:
- 动态规划结合凸包优化(Convex Hull Trick)。
示例代码:
class ConvexHullTrick:
def __init__(self):
self.hull = []
def add_line(self, a, b):
while len(self.hull) >= 2 and self._is_bad(self.hull[-2], self.hull[-1], (a, b)):
self.hull.pop()
self.hull.append((a, b))
def _is_bad(self, line1, line2, line3):
return (line3[1] - line1[1]) * (line1[0] - line2[0]) < (line2[1] - line1[1]) * (line1[0] - line3[0])
def query(self, x):
while len(self.hull) >= 2 and self._eval(self.hull[0], x) <= self._eval(self.hull[1], x):
self.hull.pop(0)
return self._eval(self.hull[0], x)
def _eval(self, line, x):
return line[0] * x + line[1]
# 示例使用
# cht = ConvexHullTrick()
# cht.add_line(a, b)
# result = cht.query(x)
HDU1224 - 3n + 1 Problem
描述:
- 给定一个整数范围,要求在该范围内找到3n+1序列长度的最大值。
推荐算法:
- 直接模拟计算。
示例代码:
def collatz_length(n):
length = 1
while n != 1:
if n % 2 == 0:
n //= 2
else:
n = 3 * n + 1
length += 1
return length
def max_collatz_in_range(start, end):
max_length = 0
for i in range(start, end + 1):
max_length = max(max_length, collatz_length(i))
return max_length
# 示例输入
# start, end = 1, 10
# result = max_collatz_in_range(start, end)
HDU1317 - Game of Connections
描述:
- 给定一个网格,每个点要么是空地要么是建筑,要求放置尽量少的路灯照亮所有空地。
推荐算法:
- 二分图最大匹配,即 Hopcroft-Karp Algorithm。
示例代码:
class BipartiteMatcher:
def __init__(self, n, m):
self.n = n
self.m = m
self.edges = [[] for _ in range(n)]
self.left_match = [-1] * n
self.right_match = [-1] * m
self.dist = [-1] * n
def add_edge(self, u, v):
self.edges[u].append(v)
def bfs(self):
queue = []
for i in range(self.n):
if self.left_match[i] == -1:
self.dist[i] = 0
queue.append(i)
else:
self.dist[i] = float('inf')
found_augmenting_path = False
for u in queue:
if self.dist[u] >= 0:
for v in self.edges[u]:
if self.right_match[v] == -1:
found_augmenting_path = True
elif self.dist[self.right_match[v]] == float('inf'):
self.dist[self.right_match[v]] = self.dist[u] + 1
queue.append(self.right_match[v])
return found_augmenting_path
def dfs(self, u):
if u != -1:
for v in self.edges[u]:
if self.right_match[v] == -1 or (self.dist[self.right_match[v]] == self.dist[u] + 1 and self.dfs(self.right_match[v])):
self.left_match[u] = v
self.right_match[v] = u
return True
self.dist[u] = float('inf')
return False
return True
def maximum_matching(self):
matching = 0
while self.bfs():
for i in range(self.n):
if self.left_match[i] == -1:
if self.dfs(i):
matching += 1
return matching
# 示例使用
# n, m = number of vertices on each side of bipartite graph
# matcher = BipartiteMatcher(n, m)
# matcher.add_edge(u, v) # 添加二分图中的边
# result = matcher.maximum_matching()