题目要求我们求出从每一个点出发所能到达的点的最大值
思路1:对每一个点采用dfs搜索,求出最大点的编号。这样从理论上来说是可行的,但是时间复杂度是O(n^2),肯定是会超时。
思路2:对原图进行缩点,每一个强联通分量都看成一个点,连接这几个点,然后进行dfs,求出每一个点能够到达的最大值作为这一个强连通分量的最大值,最后就是输出答案了。
这里采用思路2来进行解题:
①如何求出一张图的强连通分量
tarjan算法
def tarjan(v):
global time,num
vis[v]=1
stack.append(v)
dfn[v]=time
low[v]=time
time+=1
#访问v的后继节点 深度优先搜索 同时更新low
for i in range(len(point[v])):
s=point[v][i]
if dfn[side[s][1]]==0:
tarjan(side[s][1])
low[v]=min(low[v],low[side[s][1]])
elif vis[side[s][1]]==1:
low[v]=min(low[v],low[side[s][1]])
#出栈
if dfn[v]==low[v]:
num+=1
while stack[-1]!=v:
value=stack.pop()
vis[value]=0
belong[value]=num
maxp[num]=max(maxp[num],value)
value=stack.pop()
vis[value]=0
belong[value]=num
maxp[num]=max(maxp[num],value)
简单分析一下tarjan算法,我们引入两个数组dfn[ ]和low[ ],dfn记录访问第一时间点,low记录能回溯的最早时间点,再引入数组vis来记录栈中点的情况。首先我们访问第一个点,对它能到达的点进行遍历,如果当前点未被访问,则该点入栈,如果该点被访问了,看该点是否在栈中。直到某一点再无后继结点。我们在tarjan的过程中,还要更新点的low数组,让其尽可能的小。如果当前的点的dfn值与low值相同,我们进行出栈操作,知道栈顶元素不为当前元素。出栈的元素组成一个强连通分量。我们再引入belong数组与maxp数组分别记录当前点属于哪一个强连通分量,以及当前强连通分量中最大的点的值,强连通分量的序号从一开始编号,num用来存储图中一共有几个强连通分量。
通过这样的操作,我们得出了每一个点所属于的强连通分量的序号,以及对应序号的强连通分量中序号最大的点的值
②重新构图
我们将每一个强连通分量都看成一个点,也就是我们所谓的缩点,然后我们遍历一开始的每一条边,对于一条边的两个顶点,如果属于两个不同的强连通分量,即belong值不同,我们就用一条边将这两个强连通分量连接在一个,有几个强连通分量,最后我们重构的图就有几个点。
#对于原图的每一条边 如果边的两个顶点在不同的强连通分量上
#则我们连接这两个强连通分量
def rebuild():
global m,num
newside=[0]*m
sides=0
newmap=[[] for i in range(num+1)]
for i in range(m):
if belong[side[i][0]]!=belong[side[i][1]]:
newside[sides]=[belong[side[i][0]],belong[side[i][1]]]
newmap[belong[side[i][0]]].append(sides)
sides+=1
return newside,newmap
newside记录边,newmap记录点连接的边,节省存储空间。图一共有num个顶点。由于每一个点到现在为止都有了自己所属的强连通分量的序号,而maxp数组记录某一个强连通分量中的最大值的序号,接下来我们需要做的就是看某个强连通分量中最大值能否变得更大,我们就对我们的新建的强连通分量的图进行dfs搜索即可,记录最大值即可
③dfs搜索记录最大值
def dfs(v,newside,newmap):
#强连通分量的序号从小到大 是从深到浅
if ans[v]:
return
ans[v]=maxp[v]
for i in range(len(newmap[v])):
side=newmap[v][i]
dfs(newside[side][1],newside,newmap)
ans[v]=max(ans[v],ans[newside[side][1]])
完整代码
global n,m
global side,point
global dfn,low,vis,time
global stack
global num,belong,maxp
global ans
def tarjan(v):
global time,num
vis[v]=1
stack.append(v)
dfn[v]=time
low[v]=time
time+=1
#访问v的后继节点 深度优先搜索 同时更新low
for i in range(len(point[v])):
s=point[v][i]
if dfn[side[s][1]]==0:
tarjan(side[s][1])
low[v]=min(low[v],low[side[s][1]])
elif vis[side[s][1]]==1:
low[v]=min(low[v],low[side[s][1]])
#出栈
if dfn[v]==low[v]:
num+=1
while stack[-1]!=v:
value=stack.pop()
vis[value]=0
belong[value]=num
maxp[num]=max(maxp[num],value)
value=stack.pop()
vis[value]=0
belong[value]=num
maxp[num]=max(maxp[num],value)
#对于原图的每一条边 如果边的两个顶点在不同的强连通分量上
#则我们连接这两个强连通分量
def rebuild():
global m,num
newside=[0]*m
sides=0
newmap=[[] for i in range(num+1)]
for i in range(m):
if belong[side[i][0]]!=belong[side[i][1]]:
newside[sides]=[belong[side[i][0]],belong[side[i][1]]]
newmap[belong[side[i][0]]].append(sides)
sides+=1
return newside,newmap
def dfs(v,newside,newmap):
#强连通分量的序号从小到大 是从深到浅
if ans[v]:
return
ans[v]=maxp[v]
for i in range(len(newmap[v])):
side=newmap[v][i]
dfs(newside[side][1],newside,newmap)
ans[v]=max(ans[v],ans[newside[side][1]])
n,m=map(int,input().split())
side=[0]*m
point=[[] for i in range(n+1)]
dfn=[0]*(n+1)
low=[0]*(n+1)
vis=[0]*(n+1)
stack=[]
time=0
num=0
belong=[0]*(n+1)
maxp=[0]*(2*n+1)
for i in range(m):
side[i]=list(map(int,input().split()))
point[side[i][0]].append(i)
for i in range(1,n+1):
if not dfn[i]:
tarjan(i)
ns,nm=rebuild()
ans=[0]*(num+1)
for i in range(1,num+1):
if not ans[i]:
dfs(i,ns,nm)
for i in range(1,n+1):
print(ans[belong[i]],end=" ")
本文介绍了如何利用Tarjan算法求解图的强连通分量,并通过缩点优化时间复杂度。首先详细解释了Tarjan算法的实现过程,包括dfn、low、vis数组的作用以及如何找到强连通分量。接着,文章阐述了如何根据强连通分量重新构建图并进行DFS搜索,以找出每个点可达的最大值。最后,给出了完整的代码实现,展示了一个具体的解题过程。
475

被折叠的 条评论
为什么被折叠?



