图(Graph)
图:由顶点的有穷非空集合 V ( G ) V ( G ) V(G) 和顶点之间边的集合 E ( G ) E ( G ) E(G) 组成,通常表示为: G = ( V , E ) G = ( V , E ) G=(V,E),其中, G G G表示个图, V V V 是图 G G G 中顶点的集合, E E E 是图 G G G 中边的集合。若 V = v 1 , v 2 , . . . , v n V = { v_1 , v_2 , . . . , v_n } V=v1,v2,...,vn, 则用 ∣ V ∣ ∣ V ∣ ∣V∣表示图 G G G 中顶点的个数,也称图 G G G 的阶,E = { ( u , v ) ∣ u ∈ V , v ∈ V } ,用 ∣ E ∣ ∣ E ∣ ∣E∣表示图 G G G 中边的条数。
有向图:
无向图:
连通、连通图和连通分量:
强连通图、强连通分量:
生成树、生成森林:
顶点的度、入度和出度:
路径、路径长度和回路:
顶点
v
p
v_p
vp到顶点
v
q
v_q
vq 之间的一条路径是指顶点序列
v
p
,
v
i
1
,
v
i
2
,
.
.
.
,
v
i
m
v_p,v_{i_1},v_{i_2},...,v_{i_m}
vp,vi1,vi2,...,vim,当然关联的边也可以理解为路径的构成要素。路径上边的数目称为路径长度。第一个顶点和最后一个顶点相同的路径称为回路或环。若一个图有
n
n
n 个顶点,并且有大于
n
−
1
n − 1
n−1 条边,则此图一定有环。
1. 图的表示
1.1 邻接矩阵
n,m = map(int,input().split())
g = [[0]*(n+1) for i in range(n+1)]
for i in range(m):
u,v = map(int,input().split())
g[u][v] = 1
# g[v][u] = 1 # 无向图加上
# 图的遍历
for i in range(1,n+1):
for j in range(1,n+1):
if i!=j and g[i][j]:
print(i,j)
优点: 比较好写
缺点: 二维数组对顶点数目有限制(
1
0
3
10^3
103 左右),否则内存会超;对于稀疏图来说,数组利用率太低
空间耗费:
O
(
N
2
)
O(N^2)
O(N2)
1.2 邻接表
当一个图为稀疏图时(边数相对顶点较少),使用邻接矩阵法显然要浪费大量的存储空间;
n,m = map(int,input().split())
g = [[] for i in range(n+1)]
for i in range(m):
a,b = map(int,input().split())
g[a].append(b)
# g[b].append(a) # 无向图加上这句
for i in range(1,n+1):
for j in g[i]:
print(i,j)
利用邻接表储存图:
很容易就可以算出某个顶点的入度或出度是多少,判断两顶点是否存在弧也很容易实现。
对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。
2. 图的遍历
图的遍历:我们希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次, 这一过程就叫做图的遍历(Traversing Graph)。
对于图的遍历来,通常有两种遍历次序方案: 深度优先遍历和广度优先遍历
2.1 深度优先遍历
这种搜索算法所遵循的搜索策略是尽可能“深”地搜索一个图。
它的基本思想如下:
- 首先访问图中某一起始顶点v,然后由v出发,访问与v邻接且未被访问的任一顶点 w 1 w_1 w1,再访问与 w 1 w_1 w1 邻接且未被访问的任一顶点…
- 重复上述过程。
- 当不能再继续向下访问时,依次退回到最近被访问的顶点,若它还有邻接顶点未被访问过,则从该点开始继续上述搜索过程,直至图中所有顶点均被访问过为止。
一般情况下,其递归形式的算法十分简洁,算法过程如下:
def dfs(graph,v):
visit[v] = 1
print(v,end=' ')
for next_ in graph[v]:
if not visit[next_]:
dfs(graph,next_)
def TraverseGraph(graph,n):
visit = [False]*(n+1)
for i in range(1,n+1):
if not visit[i]:
dfs(graph,i)
2.2 广度优先遍历
广度优先搜索是一种分层的查找过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有往回退的情况,因此它不是一个递归的算法。为了实现逐层的访问,算法必须借助一个辅助队列,以记忆正在访问的顶点的下一层顶点。
以下是广度优先遍历的代码:
def bfs(graph,v0):
print(v0,end=' ')
from collections import deque
que = deque()
que.append(graph[v0])
while que:
edges = que.popleft()
for v in edges:
if not visit[v]:
print(v,end=' ')
que.append(graph[v])
visit[v] = 1
3. 最短路问题
在网图和非网图中,最短路径的含义是不同的。由于非网图它没有边上的权值,所谓的最短路径,其实就是指两顶点之间经过的边数最少的路径;而对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。
3.1 单源最短路(Dijkstra)
即询问的是从单起点
S
S
S ,到其他点的最短路径。
基本步骤:
1. 对图
g
g
g 设置集合
S
S
S,存放已被访问的节点。
2. 每次从集合
V
−
S
V-S
V−S(就是所有未被访问的节点)中选择与起点
s
s
s 距离最短的节点
u
u
u,访问并加入
S
S
S 。
3. 以
u
u
u 为中介点,优化起点
s
s
s 与所有从
u
u
u 能到达的顶点
v
v
v 之间的最短距离(这个操作称为松弛)。
4. 这样的操作执行
n
n
n 次,直到所有节点均被访问。(每次操作都能确定一个点的最短路径)
基础版:
时间复杂度:
O
(
E
2
)
O(E^2)
O(E2)
def Dijkstra(start,end):
# 维护一个由起点到其它所有点的距离和判断是否访问过的数组
dist = [float('inf)]*(n+1)
visited = [False]*(n+1)
dist[start] = 0
visited[stat] = True
# 直到找到终点停止
while not visited[end]:
minv.midx = float('inf'),0
# 找出离起点最近且没被访问过的点
for i in range(n):
if not visited[i] and dist[i]<minv:
minv = dist[i]
midx = i
visited[midx] = True
# 寻找目标点能到达的其它点 并更新为最短距离
for next_,w in g[midx]:
if dist[next_] > minv+w:
dist[next_] = minv+w
return dist
堆优化 Dijkstra:
- 优化了循环中找最小 d i s t [ u ] dist[u] dist[u] 的过程:使用最小堆存储{ d i s t [ u ] , u dist[u],u dist[u],u} ,保证堆顶直接就是最小的 d i s t [ u ] dist[u] dist[u]
- 优化了以 u u u 为中介点时,循环遍历所有节点的过程:采用邻接矩阵存储,只遍历实际存在的边
def dijkstra(s):
# 单源最短路 无负边
#维护一个由起点到其它点的距离和判断是否访问过的列表
dist = [float('inf')]*(n+1)
visited = [False]*(n+1)
dist[s] = 0
que = [(0,s)]
while que:
# 找出离起点最近且没被访问过的点
curDis,curNode = heapq.heappop(que)
if visited[curNode]:
continue
visited[curNode] = True
for idx,w in g[curNode]: # 遍历从curNode可以到达的边 都变为最短距离
if dist[idx] > curDis+w:
dist[idx] = curDis+w
heapq.heappush(que, (dist[idx], idx))
return dist
时间复杂度: O ( E l o g V ) O(ElogV) O(ElogV),最坏的情况下就是每条边( E E E )都成功松弛,加入最小堆( l o g V logV logV)
Dijkstra 无法处理有负权边的情况:根源是如果存在负权边,那么就无法保证每次找出的 u u u , d i s t [ u ] dist[u] dist[u]是起点到 u u u 的所有路径中最小的。
-
无负权边的情况下,如果当次循环中 d i s t [ u ] dist[u] dist[u] 最小,就可以保证此时 d i s t [ u ] dist[u] dist[u] 就是全局最小,之后不可能找出使 d i s t [ u ] dist[u] dist[u] 更小的情况
反证:如果当次循环 d i s t [ u ] dist[u] dist[u] 最小,但假设在之后的循环中 d i s t [ u ] dist[u] dist[u] 还能被优化,那么应该选择另一个顶点 u ′ u' u′( d i s t [ u ′ ] > d i s t [ u ] dist[u'] > dist[u] dist[u′]>dist[u]),之后松弛只可能使
与松弛条件 n e w d i s t [ u ] < d i s t [ u ] new_dist[u] < dist[u] newdist[u]<dist[u]矛盾,证毕。 -
而有负权边的情况下,可能 g [ u ′ − > u ] < 0 g[u'->u] < 0 g[u′−>u]<0,所以此时 d i s t [ u ] dist[u] dist[u]不一定就是最终的最小情况
上图中设置 A A A 为源点- u = A , d i s [ A ] = 0 , 松弛 d i s t [ B ] = − 1 , d i s t [ C ] = 1 u = A,dis[A] = 0,松弛dist[B]= -1,dist[C]=1 u=A,dis[A]=0,松弛dist[B]=−1,dist[C]=1
- u = B , d i s [ B ] = − 1 , 此时 B 加入集合 S ,代表已被访问,并且松弛 d i s t [ C ] = − 6 u = B,dis[B] = -1,此时 B 加入集合 S ,代表已被访问,并且松弛dist[C]=-6 u=B,dis[B]=−1,此时B加入集合S,代表已被访问,并且松弛dist[C]=−6
- u = C , 其他点都已经被访问 , 结束算法 u = C,其他点都已经被访问,结束算法 u=C,其他点都已经被访问,结束算法
但实际上如果一开始从 A − > C − > B , d i s t [ B ] 会更小 A->C->B,dist[B]会更小 A−>C−>B,dist[B]会更小
3.2 单源最短路(负边,负环)
3.2.1 Bellman-ford
基本步骤:
- 假设图中含有 V V V 个顶点,且起点为 s s s,初始化 d i s t [ n ] dist[n] dist[n] 数组,其中 d i s t [ s ] = 0 dist[s]=0 dist[s]=0 ,其他顶点都为无穷大
- 以任意顺序松弛所有边,重复
V
−
1
V-1
V−1 轮。
那又为什么不像 Dijkstra 算法那样,会被负权边影响?
为 Dijkstra 算法中有一个控制访问的 v i s i s t e d [ n ] visisted[n] visisted[n]数组,保证每个节点只能被访问一次,不能被二次优化,但 Bellman-ford 不是,它更加通用,每条边、(每个节点)都可能被多次访问、被多次优化。
def bellman_ford(start):
# 存放边[u,v,w] : u->v 的长度为 w
edges = [
[0,1,-3],
[1,2,2],
[2,3,3],
[3,4,2],
[0,4,5]
]
# 单源最短路 解决负边问题
dist = [float('inf')]*(n+1)
dist[start] = 0
# 循环 n-1 次
for i in range(n-1):
# 从起点最多通过 i+1 条边时的最短路径
for f,t,w in edges:
if dist[t]>dist[f]+w:
dist[t] = dist[f]+w
flag = False
# 判断负权环
for f,t,w in edges:
if dist[t]>dist[f]+w:
# 如果还存在边需要松弛则存在负权环
flag = True
break
return dist
时间复杂度: O ( V E ) O(VE) O(VE), V − 1 V-1 V−1 轮迭代所有边( E E E),适合稀疏图。
3.2.2 SPFA
SPFA 是对 Bellman-ford 的优化,因为在 Bellman-ford 算法中,每轮都需要对 所有 边进行松弛,很多边的遍历都是无效的(即不满足松弛条件)。
而满足松弛条件有一个前提:只有当某一个节点 u u u 的 d i s t [ u ] dist[u] dist[u] 值在上一轮被改变了,那这一轮从 u u u 出发的边的终点 v v v 的 d i s t [ v ] dist[v] dist[v] 值才有可能被改变。(涟漪)(引理:最短路径的子路径都是最短路径)
所以,SPFA 在 Bellman-ford 算法的基础上,引入了一个队列,如果在某轮迭代中,从 u u u 出发的边 u − > v u->v u−>v满足松弛条件,那么就将 v v v 加入队列,下一次迭代中从 v v v 出发的边可能可以被松弛。
如何判断负环?
- 统计每个点入队的次数,如果某个点入队 n n n 次,则说明存在负环(每次入队,num[v]++ );
- 统计当前每个点的最短路中所包含的边数,如果某点的最短路所包含的边数大于等于 n n n ,则也说明存在负环(每次入队,num[v] = num[u] + 1 )
def SPFA(start,end,g):
from collections import deque
dist = [float('inf')]*(n+1) # 初始化距离
visited = [False]*(n+1) # 初始化访问列表
cnt = [0]*(n+1) # 记录起点到每点的最短距离包含点的个数
que = deque([start]) # 存放待优化的点
dist[start] = 0
visited[start] = True
cnt[start] = 1
while que:
curNode = que.popleft()
visited[curNode] = False # 被弹出则不在访问列表中
# 遍历 curNode 能到达的点
for idx,w in g[curNode]:
if dist[idx]>dist[curNode]+w:
dist[idx] = dist[curNode]+w
cnt[idx] = cnt[curNode]+1
# 包含点的个数超过 n 说明存在负权环
if cnt[idx]>n:
print("存在负权环")
return
if not visited[idx]:
que.append(idx)
visited[idx] = True
return dist[end]
时间复杂度 : O ( k E ) O(kE) O(kE) 其中 k k k 为常数。但如果图中有从源点可达的负环,复杂度会退化到 O ( V E ) O(VE) O(VE) ,适合稀疏图。
3.3 多源最短路
动态规划的方法:
-
f [ i , j , k ] f[i,j,k] f[i,j,k]表示从 i i i 到 j j j 的最短路径上有 1 k 1~k 1 k 中的某个点作为中介点,那么有:
f [ i , j , k ] = m i n ( f [ i , j k − 1 ] , f [ i , k , k − 1 ] + f [ k , j , k − 1 ] ) f[i,j,k] = min(f[i,jk-1],f[i,k,k-1]+f[k,j,k-1]) f[i,j,k]=min(f[i,jk−1],f[i,k,k−1]+f[k,j,k−1])解释:
1. 不用 k k k 做中介点( f [ i , j , k − 1 ] f[i,j,k-1] f[i,j,k−1] );
2. 用 k k k 做中介点( f [ i , k , k − 1 ] + f [ k , j , k − 1 ] f[i,k,k-1]+f[k,j,k-1] f[i,k,k−1]+f[k,j,k−1]) -
因为在计算第 k k k 层的 f [ i , j ] f[i,j] f[i,j] 的时候必须先将第 k − 1 k-1 k−1 层的所有状态计算出来,所以需要把 k k k 放在最外层。
# 多源最短路(n^3)
def floyd():
# 遍历每个点为中转点
for k in range(n):
# 以 k 为中转点 遍历整个图更新路径
for i in range(n):
for j in range(n):
# 判断 i 是否能够到达 k 再到达 j
if g[i][k]==float('inf') or g[k][j]==float('inf'):
continue
g[i][j] = min(g[i][j],g[i][k]+g[k][j])
n = 4
g = [
[0,2,float('inf'),6],
[2,0,3,2],
[float('inf'),3,0,2],
[6,2,2,0]
]
时间复杂度: O ( N 3 ) O(N^3) O(N3),适合稠密图
4. 最小生成树
生成树(极小连通子图): 能连通无向图的所有( N 个)顶点而又不产生回路的任何子图(N-1 条边)。
最小生成树: 所有生成树中带权路径和最小的那棵树。
4.1 Prim
与 Dijkstra 算法的思路基本一致,设定一个已访问的顶点集合
S
S
S ,每次从未访问的节点中,选择一个与 集合
S
S
S 距离最小的节点(Dijkstra 算法中是选择一个与起点距离最小的节点),对它的邻边进行优化。
如何理解与 集合
S
S
S 距离最小?设
v
v
v 是一个未访问的点,则
v
v
v 与集合
S
S
S 的距离为:
每次需要找到这个具有最小的
L
L
L 的
v
v
v :
时间复杂度: 和 Dijkstra 算法一样,也可以用最小堆进行优化。
4.2 Kruskal
算法步骤:
- 将所有边按权重从小打大进行排序。
- 按边权重从小到大测试所有边,如果当前测试边所连接的两个顶点不在同一个连通块中,则把这条边加入当前最小生成树中;否则,将其舍弃。
- 执行步骤 2,直到最小生成树的边数等于总顶点数减 1,或是所有边都遍历完毕。
判断两个节点是否在一个连通块中,需要使用并查集;
#最小生成树(利用并查集来寻找代表元)
def find(x):
r=x
while r!=pre[r]:
r=pre[r]
while x!=r: #压缩路径
j=pre[x]
pre[x]=r
x=j
return r
def join(x,y):
fx,fy=find(x),find(y)
if fx!=fy:
pre[fx]=fy
return True
return False
n,m = map(int,input().split())
mst,edges = [],[] # 最小生成树的序列, 存储边的列表
pre = [i for i in range(n+1)] # 每个顶点的代表元
for i in range(m):
x,y,w = map(int,input().split())
edges.append((w,x,y))
length = 0
edges.sort() # 将边以权值从小到大排序
for w,x,y in edges:
if join(x,y): # 用于判断两顶点是否为不同连通分量
mst.append(((x,y),w))
length += w
if len(mst) == n-1: # 直至图中加入n-1条边
print(length)
break
else:
print("orz")
5. 拓扑排序
拓扑排序是将有向无环图 的所有顶点排成一个线性序列,使得对图 中的任意两个顶点 ,如果存在边 ,那么在序列中 一定在 前面。但同一层节点的先后关系无严格限制。
算法步骤:
- 定义一个队列 Q Q Q ,并把所有入度 为 0 的节点加入队列。(因为同一层节点先后顺序不要求,所以用栈也可以)
- 取队首节点 u u u ,输出(或记录入某个数组)。然后遍历所有从它出发的边,将这些边的目的顶点 v v v 的入度减 1 (即删去这些边)。如果某个顶点的入度变成了 0 ,就将其加入队列。
- 反复执行 ② 操作,直到队列为空。
- 当队列为空时,如果输出或记录的节点数正好为 N N N ,就说明拓扑排序成功,并且 G G G 为有向无环图。否则失败, G G G中有环。
def topoSort(g,in_):
import collections
que = collections.deque()
for i in range(1,n+1):
if in_[i]==0:
que.append(i)
ans = []
while que:
cur = que.popleft()
ans.append(cur)
for next in g[cur]:
in_[next]-=1
if in_[next] == 0:
que.append(next)
return ans
习题
#Dijkstra
P4779 【模板】单源最短路径(标准版)
P1576 最小花费
P1629 邮递员送信
P1529 [USACO2.4]回家 Bessie Come Home
P1744 采购特价商品
P1821 [USACO07FEB] Cow Party S
n,m,x = map(int,input().split())
g1 = [[] for i in range(n+1)]
g2 = [[] for i in range(n+1)]
# 有向图
#求 一个单源最短路 + 一个单终点最短路 的最大值。
'''
反向建图,直接把单终点最短路转为单源最短路,只需要跑两次最短路算法
'''
for i in range(m):
u,v,w = map(int,input().split())
g1[u].append((w,v))
g2[v].append((w,u))
import heapq
def dijkstra(s,g):
dist = [float('inf')]*(n+1)
visited = [0]*(n+1)
que = [(0,s)]
dist[s] = 0
while que:
curDis,curNode = heapq.heappop(que)
if visited[curNode]:
continue
visited[curNode] = 1
for w,idx in g[curNode]:
if dist[idx]>curDis+w:
dist[idx] = curDis+w
heapq.heappush(que,(dist[idx],idx))
return dist
dis1 = dijkstra(x,g1)
dis2 = dijkstra(x,g2)
ans = [0]*(n+1)
for i in range(1,n+1):
ans[i] = dis1[i]+dis2[i]
print(max(ans))
P2299 Mzc和体委的争夺战
P2951 [USACO09OPEN]Hide and Seek S
P2984 [USACO10FEB]Chocolate Giving S
P1339 [USACO09OCT]Heat Wave G
P6770 [USACO05MAR]Checking an Alibi 不在场的证明
#Spfa
P1938 [USACO09NOV]Job Hunt S
'''
这道题完美地需要我们将点权转成边权:
贝西需要去这么多城市,而每座城市他都要赚 D 美元。那我们为什么不直接将每条普通边的边权就设置成 D 呢?这两者之间是等价的呀·····
那对于飞行边来说,同理,飞行边需要花费 T 美元,而到一个城市又可以赚 D 美元,那我们就可以将飞行边权设为 D-T。
这样,存图的事就解决了
2. 只要你读懂题目,就会发现这题压根不是最短路,是最长路······
做最长路主要有两种做法:
2.1 做最短路是我们是现将 dis 数组设为无穷大,再每次更新最小值。反过来,最长路就可以将 dis 数组都设为 0 ,再每次更新最大值。
2.2 将所有边权都取反后,再求最短路
'''
D,P,C,F,S = map(int,input().split())
g = [[] for i in range(C+1)]
for i in range(P):
a,b = map(int,input().split())
g[a].append((D,b))
for i in range(F):
a,b,w = map(int,input().split())
g[a].append((D-w,b))
from collections import deque
def SPFA(s,g):
dist = [0]*(C+1)
visited = [0]*(C+1)
cnt = [0]*(C+1)
que = deque([s])
visited[s] = 1
cnt[s] = 1
dist[s] = D
while que:
curNode = que.popleft()
visited[curNode] = 0
for w,idx in g[curNode]:
if dist[idx]<w+dist[curNode]:
dist[idx] = w+dist[curNode]
cnt[idx] = cnt[curNode]+1
if cnt[idx]>C:
return []
if not visited[idx]:
que.append(idx)
visited[idx] = 1
return dist
dist = SPFA(S,g)
if len(dist)==0:
print(-1)
else:
print(max(dist))
P3906 Geodetic集合
n,m = map(int,input().split())
'''
先用 spfa 单源最短路径算出 v 点到其他点的最短距离,用dis1数组存储。
然后再用 spfa 算出 u 点到其他点的最短距离,用 dis2 数组存储。
算完最短距离后开始遍历每个点,
如果这个点到 v 的最短路径(dis1[i])加上这个点到 u 的最短路径(dis2[i])恰好等于 v 到 u 的最短距离(dis1[u]),那么就输出这个点的编号。
'''
g = [[] for i in range(n+1)]
for i in range(m):
u,v = map(int,input().split())
g[u].append((1,v))
g[v].append((1,u))
k = int(input())
from collections import deque
def SPFA(s,g):
dist = [float('inf')]*(n+1)
visited = [0]*(n+1)
que = deque([s])
visited[s] = 1
dist[s] = 0
while que:
curNode = que.popleft()
visited[curNode] = 0
for w,idx in g[curNode]:
if dist[idx]>w+dist[curNode]:
dist[idx] = w+dist[curNode]
if not visited[idx]:
que.append(idx)
visited[idx] = 1
return dist
for i in range(k):
s,e = map(int,input().split())
dis1 = SPFA(s,g)
dis2 = SPFA(e,g)
ans = []
for i in range(1,n+1):
if dis1[i]+dis2[i]==dis1[e]:
ans.append(i)
print(*ans)
P1144 最短路计数
n,m = map(int,input().split())
g = [[] for i in range(n+1)]
edges = []
for i in range(m):
a,b = map(int,input().split())
g[a].append((1,b))
g[b].append((1,a))
edges.append((a,b))
from collections import deque
ans = [0]*(n+1)
ans[1] = 1
def SPFA(s,g):
dist = [float('inf')]*(n+1)
visited = [0]*(n+1)
cnt = [0]*(n+1)
que = deque([s])
cnt[s] = 1
visited[s] = 1
dist[s] = 0
while que:
curNode = que.popleft()
visited[curNode] = 0
for w,idx in g[curNode]:
if dist[idx]>w+dist[curNode]:
dist[idx] = w+dist[curNode]
ans[idx] = ans[curNode] # idx 和 curNode 处于同一条最短路上
cnt[idx] = cnt[curNode]+1
if cnt[idx]>n:
return
if not visited[idx]:
que.append(idx)
visited[idx] = 1
elif dist[idx]==w+dist[curNode]: # 该点最短路个数为 idx 最短路个数+ curNode最短路个数
ans[idx] = (ans[idx]+ans[curNode])%100003
return
dis1 = SPFA(1,g)
for i in range(1,n+1):
print(ans[i])
P2850 [USACO06DEC]Wormholes G
t = int(input())
'''
此题就是求一个图有没有负权回路。
虫洞就是边权为负的边。
用spfa判负权回路:从随便一个点出发,当某个点松弛了超过n次后,便可以说明有负环
'''
from collections import deque
def SPFA(s,g):
n = len(g)
dist = [float('inf')]*(n)
visited = [0]*n
cnt = [0]*n
que = deque([s])
dist[s] = 0
cnt[s] = 1
visited[s] = 1
while que:
u = que.popleft()
visited[u] = 0
for w,v in g[u]:
if dist[v]>w+dist[u]:
dist[v] = w+dist[u]
cnt[v] = cnt[u]+1
if cnt[v]>n-1:
return True
if not visited[v]:
que.append(v)
visited[v] = 1
return False
for s in range(t):
n,m,w = map(int,input().split())
g = [[] for i in range(n+1)]
for i in range(m):
u,v,p = map(int,input().split())
g[u].append((p,v))
g[v].append((p,u))
for i in range(w):
u,v,p = map(int,input().split())
g[u].append((-p,v))
if SPFA(1,g):
print('YES')
else:
print('NO')
#Floyd
力扣1334. 阈值距离内邻居最少的城市
P2910 [USACO08OPEN]Clear And Present Danger S
P1119 灾后重建
Floyd这个算法的主要思路,就是通过其他的点进行中转来求的两点之间的最短路。
for k in range(n):
for i in range(n):
for j in range(n):
if graph[i][k]==float('inf') or graph[k][j]==float('inf'):
continue
graph[i][j] = min(graph[i][j],graph[i][k]+graph[k][j])
这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。
而我们再回头看题意:
所有的边全部给出,按照时间顺序更新每一个可用的点(即修建好村庄),对于每个时间点进行两点之间询问,求对于目前建设的所有村庄来说任意两点之间的最短路
不正好就是Floyd算法中使用前k个节点更新最短路的思维吗?
n,m = map(int,input().split())
ls_t = list(map(int,input().split()))
graph = [[float('inf')]*n for i in range(n)]
for i in range(n):
graph[i][i] = 0
for i in range(m):
a,b,w = map(int,input().split())
graph[a][b] = w
graph[b][a] = w
q = int(input())
visited = [0]*(n+1)
for i in range(q):
x,y,t = map(int,input().split())
for k in range(n):
if ls_t[k]<=t and not visited[k]:
visited[k]=1
for i in range(n):
for j in range(n):
if graph[i][k]==float('inf') or graph[k][j]==float('inf') or i==j:continue
graph[i][j] = min(graph[i][j],graph[i][k]+graph[k][j])
if ls_t[x]<=t and ls_t[y]<=t and graph[x][y]!=float('inf'):
print(graph[x][y])
else:
print(-1)
#最小生成树
重点在图的建立
P1547 [USACO05MAR]Out of Hay S
P1991 无线通讯网
P2916 [USACO08NOV]Cheering up the Cow G
'''与奶牛谈话也需要时间,而且每条路都需要来回走两遍。
所以每条边的权值为:起点权值+终点权值+路的长度*2.
然后就能直接跑最小生成树了
'''
n,m = map(int,input().split())
min_=float('inf')
cs = []
for i in range(n):
s = int(input())
min_=min(min_,s)
cs.append(s)
edges = []
for i in range(m):
a,b,w = map(int,input().split())
edges.append((2*w+cs[a-1]+cs[b-1],a,b))
ans = 0
pre = [i for i in range(n)]
def find(x):
r=x
while r!=pre[r]:
r=pre[r]
while x!=r:
pre[x],x=r,pre[x]
return x
edges.sort()
cnt=0
for w,a,b in edges:
fa,fb = find(a-1),find(b-1)
if fa!=fb:
pre[fa]=fb
cnt+=1
ans+=w
if cnt==n-1:
print(ans+min_) #加劝说出发点的奶牛
break
P2330 [SCOI2005]繁忙的都市
P2820 局域网
P4047 [JSOI2010]部落划分
# 一共有k个部落,我们只需要找n-k条边得到k个连通分量,第n-k+1条边就是答案。
n,k = map(int,input().split())
locs = []
for i in range(n):
x,y = map(int,input().split())
locs.append((x,y))
edges = []
import math
for i in range(n):
x,y = locs[i]
for j in range(i+1,n):
x2,y2 = locs[j]
w = math.sqrt((x-x2)**2+(y-y2)**2)
edges.append((w,i,j))
edges.sort()
max_ = -1
pre = [i for i in range(n)]
def find(x):
r = x
while r!=pre[r]:r=pre[r]
while x!=r:pre[x],x=r,pre[x]
return x
cnt = 0
for i in range(len(edges)):
w,a,b = edges[i]
fa,fb = find(a),find(b)
if fa!=fb:
pre[fa]=fb
cnt+=1
if cnt==n-k+1:
print('{:.2f}'.format(edges[i][0]))
break
#并查集
P1551 亲戚
P2078 朋友
P1111 修复公路
P8654 [蓝桥杯 2017 国 C] 合根植物
P1536 村村通
P2256 一中校运会之百米跑