tarjan算法
LCA算法总结

LCA算法总结LCA问题(Least Common Ancestors,最近公共祖先问题),是指给定⼀棵有根树T,给出若⼲个查询LCA(u, v)(通常查询数量较⼤),每次求树T 中两个顶点u和v的最近公共祖先,即找⼀个节点,同时是u和v的祖先,并且深度尽可能⼤(尽可能远离树根)。
LCA问题有很多解法:线段树、Tarjan算法、跳表、RMQ与LCA互相转化等。
⼀ LCA问题LCA问题的⼀般形式:给定⼀棵有根树,给出若⼲个查询,每个查询要求指定节点u和v的最近公共祖先。
LCA问题有两类解决思路:在线算法,每次读⼊⼀个查询,处理这个查询,给出答案。
离线算法,⼀次性读⼊所有查询,统⼀进⾏处理,给出所有答案。
⼀个LCA的例⼦如下。
⽐如节点1和6的LCA为0。
⼆、Tarjan算法Tarjan算法是离线算法,基于后序DFS(深度优先搜索)和并查集。
算法从根节点root开始搜索,每次递归搜索所有的⼦树,然后处理跟当前根节点相关的所有查询。
算法⽤集合表⽰⼀类节点,这些节点跟集合外的点的LCA都⼀样,并把这个LCA设为这个集合的祖先。
当搜索到节点x时,创建⼀个由x本⾝组成的集合,这个集合的祖先为x⾃⼰。
然后递归搜索x的所有⼉⼦节点。
当⼀个⼦节点搜索完毕时,把⼦节点的集合与x节点的集合合并,并把合并后的集合的祖先设为x。
因为这棵⼦树内的查询已经处理完,x的其他⼦树节点跟这棵⼦树节点的LCA都是⼀样的,都为当前根节点x。
所有⼦树处理完毕之后,处理当前根节点x相关的查询。
遍历x的所有查询,如果查询的另⼀个节点v已经访问过了,那么x和v的LCA即为v所在集合的祖先。
其中关于集合的操作都是使⽤并查集⾼效完成。
算法的复杂度为,O(n)搜索所有节点,搜索每个节点时会遍历这个节点相关的所有查询。
如果总的查询个数为m,则总的复杂度为O(n+m)。
⽐如上⾯的例⼦中,前⾯处理的节点的顺序为4->7->5->1->0->…。
信息学奥赛一本通(提高组)

信息学奥赛⼀本通(提⾼组)⼀、贪⼼算法选择不相交区间问题:给定n个开区间,选择尽量多个区间,是得这些区间两两没有公共点。
(例:活动安排) 按照结束时间由⼩到⼤的顺序排列,依次考虑各个活动,如果没有和已经选择的活动冲突,就选;否则就不选。
区间选点问题:给定n个闭区间,在数轴上选尽量少的点,是得每个区间内都⾄少有⼀个点(不同区间内含的点可以是同⼀个)。
(例:种树) ⾸先按照区间的结束位置从⼩到⼤排列。
然后在区间中进⾏选择:对于当前区间,若集合中的点不能覆盖它,则将区间末尾的数加⼊集合。
贪⼼策略:取最后⼀个。
区间覆盖问题:给定n隔壁区间,选择尽量少的区间覆盖⼀条指定的线段区间。
(例:喷⽔装置) 将所有区间按照左端点由⼩到⼤排序,依次处理每个区间。
每次选择覆盖点s的区间中右端点坐标中最⼤的⼀个,并将s更新为该区间的右端点坐标,直到选择的区间包含t。
贪⼼策略:在某时刻的s,找出⼀个满⾜a[i]<=s的b[i]最⼤值即可。
流⽔作业调度问题:n作业,两机器,先a后b,求总时间最短。
(例:加⼯⽣产调度) 直观:让a没有空闲,让b空的少 Johnson算法:对于a<b的集合,按s⾮减序排列;对于a>=b的集合,按照b⾮升序排列带期限和罚款的单位时间任务调度:n任务,每个都能在单位时间内完成,每个都有对应的完成期限及完成不了的罚款数额,确定执⾏顺序使罚款最少。
(例:智⼒⼤冲浪) 按照罚款数额由⼤到⼩排序,然后依次进⾏安排。
安排规则为:使处理当前任务的时间在既在期限之内,⼜尽量靠后,如果都已经排满,则放弃处理并扔在最后.⼆、⼆分(单调性)与三分(单峰性)⼆分的边界问题:⼆分常见模型:⼆分答案(将最优化问题转为判定性问题),⼆分查找(求解分界点),代替三分(⼆分导函数求极值,定义域通常定为整数域)。
三分:任取两点判断好坏不断缩⼩区间。
三,搜索dfs的优化技巧:优化搜索顺序(对象),排除等效冗余,可⾏性剪枝(上下界剪枝),最优性剪枝,记忆化。
最小公共祖先(_LCA_)问题

i i+2j-1-1
M[i, j - 1] M[i 2 j-1 , j 1]
i+2j-1
a1
...
...
an
时间:O(nlogn)。
RMQ 的稀疏表(Sparse Table)算法
2k elements
2k elements
...
时间:O(1)。 总复杂度: O(n log n), O(1)
±1 RMQ – 分段
A’:每一段的最小值 B:此段最小值的下标
A’[0] A’[i]
A’[2n/logn]
…
B[0] B[i]
...
B[2n/logn]
…
...
...
...
...
总共:
f (2n 1) O(n), g (2n 1) O(1)
RMQ-LCA在线算法部分代码
bool visit[MAXNODE]; int oula[2*MAXNODE],dist[MAXNODE],pos[MAXN ODE],minrmq[18][2*MAXNODE],id[MAXNODE]; int n,m; void add(int u, int v, int w) { edge[e].to = v; edge[e].w = w; edge[e].next = head[u]; head[u] = e++; } void init(){ rst(head,-1); rst(visit,false); tmpdfn=0; index=0; e=0; }
Tarjan详解

Tarjan详解⾸先,tarjan是⼲什么⽤的?在学之前,我就知道⼀个名为“缩点”的模板题要⽤tarjan算法来解决,所以我对这个算法是这样理解的。
把⼀堆点在不影响题⽬的情况下缩成⼀个点,以转化为DAG(有向⽆环图)快速求解。
其实我觉得模板题正⼤⼤体现了tarjan的优势,就拿模板题来讲⼀讲这个算法吧。
思路这题⾮常好的体现了tarjan的优势和⽤武之处。
如果⼀条边和⼀个点能⽆数次重复经过,那么也就是说只要两个点能相互到达,我们就可以把它看做⼀个点。
由于每个点的点权都是正的,那么缩成⼀个点绝对不会影响结果。
正确性很显然,如果你能两个点⼀起拿再回来,那为什么不拿呢?所以说只要把所有的强联通分量缩成⼀个点,然后进⾏拓扑排序dp即可。
代码说起来简单,但是代码如何实现呢?以下参考了《算法竞赛进阶指南》:⾸先从任意⼀个点开始深搜,搜到出度为0的节点停⽌,不重复经过同⼀个点,这样搜完之后将形成的树称为“搜索树”。
还有⼀个“强联通分量”的定义,按我⾃⼰的理解,就是⼀堆点都能通过它们之间的路径相互到达,那么这些点就构成⼀个强联通分量,也就是我们要执⾏缩点的⽬标。
开⼀个栈,来记录节点之间的⽗⼦关系。
dfn表⽰时间戳,第i个被访问的点时间戳就是i,low表⽰以这个点为⼦树的根结点,能访问到的最⼩编号的节点的编号。
由于我们是按搜索树编号的,所以很显然,搜索树上⼀条边的终点时间戳肯定⽐起点⼤。
所以说,如果两点能相互到达,那么肯定会更新这个点的low,所以说就是⼀个强联通分量,⽽且两点中间中间的所有具有⽗⼦关系的点都可以相互到达。
所以说如果⼀个点的low和dfn相等的话,那么它就已经不能和后⾯的点构成强联通分量了,就只能往前找,去靠拢前⾯的点。
void tarjan(int x){low[x]=dfn[x]=++num;sta[++top]=x;ins[x]=1;for(int i=head[x];i;i=Nxt[i]){int y=ver[i];if(!dfn[y]){//没有访问过就继续搜tarjan(y);low[x]=min(low[x],low[y]);//更新low}else{if(ins[y]){//如果有夫⼦关系,那么也更新low[x]=min(low[x],dfn[y]);}}}if(low[x]==dfn[x]){//这个点已经到头了,那么往回找while(1){int y=sta[top];ins[y]=0;//⼀定要出栈,因为它已经跟后⾯的点构不成强联通分量了c[y]=x;top--;if(x!=y){p[x]+=p[y];}else{break;}}}}这样tarjan就差不多了(主要是写给⾃⼰看的)然后再拓扑排序⼀下,简单dp就搞定了完整代码:#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#define ll long longusing namespace std;const int N=200005,M=400005;int n,m,tot,top,tc,num,cnt;ll c[N],head[N],edge[M],Nxt[M],ver[M],low[N],dfn[N],sta[N],p[N],w[N],Nc[M],vc[M],hc[N],f[N],in[N],ans;bool ins[N];queue<int> q;void add(int x,int y){ver[++tot]=y;Nxt[tot]=head[x];head[x]=tot;}void add_c(int x,int y){vc[++tc]=y;Nc[tc]=hc[x];hc[x]=tc;}void tarjan(int x){low[x]=dfn[x]=++num;sta[++top]=x;ins[x]=1;for(int i=head[x];i;i=Nxt[i]){ int y=ver[i];if(!dfn[y]){tarjan(y);low[x]=min(low[x],low[y]); }else{if(ins[y]){low[x]=min(low[x],dfn[y]); }}}if(low[x]==dfn[x]){while(1){int y=sta[top];ins[y]=0;c[y]=x;top--;if(x!=y){p[x]+=p[y];}else{break;}}}}void topo(){for(int i=1;i<=n;i++){if(!in[i]&&c[i]==i){q.push(i);f[i]=p[i];// printf("%d\n",f[i]);}}while(!q.empty()){int x=q.front();q.pop();for(int i=hc[x];i;i=Nc[i]){int y=vc[i];in[y]--;f[y]=max(f[y],f[x]+p[y]); if(in[y]==0){q.push(y);}}}for(int i=1;i<=n;i++){ans=max(ans,f[i]);}}int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){scanf("%lld",&p[i]);}for(int i=1;i<=m;i++){int x,y;scanf("%d%d",&x,&y);add(x,y);}for(int i=1;i<=n;i++){if(!dfn[i]) tarjan(i);}for(int x=1;x<=n;x++){for(int i=head[x];i;i=Nxt[i]){ int y=ver[i];if(c[x]==c[y]) continue;add_c(c[x],c[y]);in[c[y]]++;}}topo();printf("%lld\n",ans);return 0;}例题:思路发现这个题貌似⽐板⼦题还简单,都不⽤缩完点之后拓扑排序,缩完点直接看有⼏个点⼊度为0即可,因为⼊度为0的点不可能通过别的节点到达。
算法学习:图论之图的割点,桥,双连通分支

图的割点、桥与双连通分支[点连通度与边连通度]在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。
一个图的点连通度的定义为,最小割点集合中的顶点数。
类似的,如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。
一个图的边连通度的定义为,最小割边集合中的边数。
注:以上定义的意思是,即有可能删除两个或两个以上点的时候才能形成多个连通块![双连通图、割点与桥]如果一个无向连通图的点连通度大于1,则称该图是点双连通的(point biconnected),简称双连通或重连通。
一个图有割点,当且仅当这个图的点连通度为1,则割点集合的唯一元素被称为割点(cut point),又叫关节点(articulation point)。
如果一个无向连通图的边连通度大于1,则称该图是边双连通的(edge biconnected),简称双连通或重连通。
一个图有桥,当且仅当这个图的边连通度为1,则割边集合的唯一元素被称为桥(bridge),又叫关节边(articulation edge)。
可以看出,点双连通与边双连通都可以简称为双连通,它们之间是有着某种联系的,下文中提到的双连通,均既可指点双连通,又可指边双连通。
[双连通分支]在图G的所有子图G’中,如果G’是双连通的,则称G’为双连通子图。
如果一个双连通子图G’它不是任何一个双连通子图的真子集,则G’为极大双连通子图。
双连通分支(biconnected component),或重连通分支,就是图的极大双连通子图。
特殊的,点双连通分支又叫做块。
[求割点与桥]该算法是R.Tarjan发明的。
对图深度优先搜索,定义DFS(u)为u在搜索树(以下简称为树)中被遍历到的次序号。
定义Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点,即DFS序号最小的节点。
找基本割集的简单方法

找基本割集的简单方法一、背景介绍基本割集是图论中的一个重要概念,它是指在一个连通图中,删去某个边或节点后使得原来的图不再连通的最小集合。
找到基本割集可以帮助我们更好地理解图的结构和性质,因此在实际应用中具有广泛的应用价值。
二、定义及性质1. 定义:在一个连通图G=(V,E)中,如果删去某个边或节点后使得原来的图不再连通,则这个边或节点被称为该图的割点或割边;如果这个割点或割边所组成的集合是该图不同联通分量之间唯一的,则称这个集合为该图的基本割集。
2. 性质:(1)每个基本割集都至少包含一个割点或者一条割边;(2)对于任意两个不同联通分量之间只有唯一一条路径;(3)将任意一个基本割集划分成两部分,则这两部分所对应的子图均为联通图。
三、找基本割集方法1. 基于DFS算法深度优先搜索算法(DFS)可以遍历整张连通图,并根据遍历顺序来确定每个节点的遍历顺序。
在DFS遍历的过程中,如果我们发现某个节点的子节点不再与该节点相连,则说明该节点是一个割点,而该节点所连接的两个子图就是一个基本割集。
具体步骤如下:(1)从任意一个节点开始进行DFS遍历;(2)记录每个节点的遍历顺序和最早访问时间;(3)对于每个非根节点v,如果存在一个子节点w,满足dfn[w]<low[v],则说明v是一个割点;(4)对于每个连通分量,将其所有割点和相应子图组成的集合作为一个基本割集。
2. 基于BFS算法广度优先搜索算法(BFS)也可以用来找到基本割集。
具体步骤如下:(1)从任意一个节点开始进行BFS遍历;(2)记录每个节点的层数和最早访问时间;(3)对于每个非根节点v,如果存在一个子节点w且dfn[w]>=depth[v],则说明v是一个割点;(4)对于每个连通分量,将其所有割点和相应子图组成的集合作为一个基本割集。
3. 基于Tarjan算法Tarjan算法是一种高效的寻找强连通分量的算法,在寻找强连通分量的过程中可以顺带找到基本割集。
环路识别 算法
环路识别算法
环路识别算法是指通过分析一个给定的网络或图结构,判断其中是否存在环路。
以下是几种常见的环路识别算法:
1. 深度优先搜索(DFS):从一个节点开始进行深度优先搜索,记录访问过的节点并标记为已访问。
如果在搜索路径中遇到已访问的节点,则说明存在环路。
2. 广度优先搜索(BFS):从一个节点开始进行广度优先搜索,使用队列来保存待访问的节点。
在搜索过程中,如果遇到已访问的节点,则说明存在环路。
3. 强连通分量算法:强连通分量是指一个图中的节点集合,其中的任意两个节点都可以相互到达。
通过使用强连通分量算法(如Tarjan算法或Kosaraju算法),可以将图划分为多个强连通分量。
如果存在一个强连通分量的大小大于1,则说明存在环路。
4. 拓扑排序:拓扑排序是一种对有向无环图(DAG)进行排序的算法。
在拓扑排序过程中,将入度为0的节点依次加入排序结果中,并将其邻接节点的入度减1。
如果最终排序结果包含所有的节点,则说明不存在环路;反之,存在环路。
这些算法可以根据具体的需求和应用场景进行选择和优化。
强连通分量个数的最小值
强连通分量个数的最小值1. 引言在图论中,强连通分量是指图中的一组顶点,其中任意两个顶点都存在一条有向路径。
强连通分量个数的最小值是指在一个有向图中,最少需要将多少个顶点组成一个强连通分量。
本文将介绍强连通分量的概念、计算方法以及如何求解强连通分量个数的最小值。
2. 强连通分量的定义在有向图中,如果从顶点A到顶点B存在一条有向路径,同时从顶点B到顶点A也存在一条有向路径,则称顶点A和顶点B是强连通的。
如果一个有向图中的每个顶点都与其他所有顶点强连通,则该有向图被称为强连通图。
而强连通分量则是指有向图中的一组顶点,其中任意两个顶点都是强连通的,且不与其他顶点强连通。
3. 强连通分量的计算方法为了计算一个有向图的强连通分量,可以使用强连通分量算法,其中最常用的是Tarjan算法和Kosaraju算法。
3.1 Tarjan算法Tarjan算法是一种深度优先搜索算法,用于寻找有向图的强连通分量。
算法的基本思想是通过DFS遍历图中的每个顶点,并记录每个顶点的遍历次序和能够到达的最小顶点次序。
通过这些信息,可以判断顶点是否属于同一个强连通分量。
具体步骤如下:1.初始化一个空栈和一个空的遍历次序数组。
2.对于每个未遍历的顶点,进行深度优先搜索。
3.搜索过程中,记录每个顶点的遍历次序和能够到达的最小顶点次序,并将顶点加入栈中。
4.当搜索完成后,根据遍历次序和能够到达的最小顶点次序,可以确定每个顶点所属的强连通分量。
3.2 Kosaraju算法Kosaraju算法是另一种用于计算有向图强连通分量的算法。
算法的基本思想是通过两次深度优先搜索来确定强连通分量。
具体步骤如下:1.对原始图进行一次深度优先搜索,记录顶点的遍历次序。
2.对原始图的转置图(即将所有边的方向反转)进行一次深度优先搜索,按照遍历次序对顶点进行访问。
3.访问过程中,可以确定每个顶点所属的强连通分量。
4. 求解强连通分量个数的最小值要求解强连通分量个数的最小值,可以使用以下方法:1.使用Tarjan算法或Kosaraju算法计算有向图的强连通分量。
tarjan 模板题
tarjan 模板题Tarjan算法是一个用于检测图中的强连通分量的算法。
以下是一个使用Python实现的Tarjan算法模板:```pythonclass Graph:def __init__(self, vertices):= vertices= []def add_edge(self, u, v):([u, v])函数用于获取最小的强连通分量编号def get_low(self, u, visited, low):visited[u] = Truelow[u] = low[u - 1] + 1for v in [u]:if visited[v] == False:low = _low(v, visited, low)elif low[v] > low[u]:low[u] = low[v]return low[u]函数用于获取强连通分量的数量和每个强连通分量的顶点def find_SCCs(self):visited = [False] ()low = [0] ()SCC_count = 0 强连通分量的数量SCCs = [] 存储所有强连通分量的顶点for i in range():if visited[i] == False:low[i] = _low(i, visited, low)if low[i] == i + 1: 检测到新的强连通分量SCC_count += 1 强连通分量的数量加一([i]) 将该顶点添加到当前强连通分量中return SCC_count, SCCs 返回强连通分量的数量和每个强连通分量的顶点```使用该模板,您可以创建一个图对象,添加边,并使用`find_SCCs()`函数检测强连通分量。
例如:```pythong = Graph(5) 创建一个有5个顶点的图对象_edge(0, 1) 添加边 (0, 1)_edge(1, 2) 添加边 (1, 2)_edge(2, 3) 添加边 (2, 3)_edge(3, 4) 添加边 (3, 4)_edge(4, 0) 添加边 (4, 0)SCC_count, SCCs = _SCCs() 检测强连通分量print("强连通分量的数量:", SCC_count) 输出: 强连通分量的数量: 1 print("第一个强连通分量的顶点:", SCCs[0]) 输出: 第一个强连通分量的顶点: [0, 1, 2, 3, 4]```。
LCA算法——精选推荐
LCA算法概况CA(Lowest Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。
基本介绍LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。
对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表⽰⼀个结点x,满⾜x是u、v的祖先且x的深度尽可能⼤。
另⼀种理解⽅式是把T理解为⼀个⽆向⽆环图,⽽LCA(T,u,v)即u到v的最短路上深度最⼩的点。
这⾥给出⼀个LCA的例⼦:对于T=<V,E>V={1,2,3,4,5}E={(1,2),(1,3),(3,4),(3,5)}则有:LCA(T,5,2)=1LCA(T,3,4)=3LCA(T,4,5)=3实现暴⼒/Tarjan/DFS+ST/倍增暴⼒枚举(朴素算法)对于有根树T的两个结点u、v,⾸先将u,v中深度较深的那⼀个点向上蹦到和深度较浅的点,然后两个点⼀起向上蹦,直到蹦到同⼀个点,这个点就是u,v的最近公共祖先,记作LCA(u,v)。
但是这种⽅法的时间复杂度在极端情况下会达到O(n)。
特别是有多组数据求解时,时间复杂度将会达到O(n*m)。
例: [1]在当这棵树是⼆叉查找树的情况下,如下图:那么从树根开始:如果当前结点t ⼤于结点u、v,说明u、v都在t 的左侧,所以它们的共同祖先必定在t 的左⼦树中,故从t 的左⼦树中继续查找;如果当前结点t ⼩于结点u、v,说明u、v都在t 的右侧,所以它们的共同祖先必定在t 的右⼦树中,故从t 的右⼦树中继续查找;如果当前结点t 满⾜ u <t < v,说明u和v分居在t 的两侧,故当前结点t 即为最近公共祖先;⽽如果u是v的祖先,那么返回u的⽗结点,同理,如果v是u的祖先,那么返回v的⽗结点。
[2]C++代码如下:12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19int query(Node t, Node u, Node v) {int left = u.value;int right = v.value;//⼆叉查找树内,如果左结点⼤于右结点,不对,交换 if(left > right) {int temp = left;left = right;right = temp;}while(true) {//如果t⼩于u、v,往t的右⼦树中查找if(t.value < left)t = t.right; //如果t⼤于u、v,往t的左⼦树中查找 else if(t.value > right)t = t.left;elsereturn t.value;}}运⽤DFS序DFS序就是⽤DFS⽅法遍历整棵树得到的序列。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
【功能】
Tarjan算法的用途之一是,求一个有向图G=(V,E)里极大强连通分量。强连通分量是指
有向图G里顶点间能互相到达的子图。而如果一个强连通分量已经没有被其它强通分量完
全包含的话,那么这个强连通分量就是极大强连通分量。
【算法思想】
用dfs遍历G中的每个顶点,通dfn[i]表示dfs时达到顶点i的时间,low[i]表示i所能
直接或间接达到时间最小的顶点。(实际操作中low[i]不一定最小,但不会影响程序的最终结
果)
程序开始时,time初始化为0,在dfs遍历到v时,low[v]=dfn[v]=time++,
v入栈(这里的栈不是dfs的递归时系统弄出来的栈)扫描一遍v所能直接达到的顶点k,
如果 k没有被访问过那么先dfs遍历k,low[v]=min(low[v],low[k]);如果k在栈里,那么
low[v]=min(low[v],dfn[k])(就是这里使得low[v]不一定最小,但不会影响到这里的low[v]会小
于dfn[v])。扫描完所有的k以后,如果low[v]=dfn[v]时,栈里v以及v以上的顶点全部出
栈,且刚刚出栈的就是一个极大强连通分量。
【大概的证明】
1. 在栈里,当dfs遍历到v,而且已经遍历完v所能直接到达的顶点时,low[v]=dfn[v]
时,v一定能到达栈里v上面的顶点:
因为当dfs遍历到v,而且已经dfs递归调用完v所能直接到达的顶点时(假设上面没
有low=dfn),这时如果发现low[v]=dfn[v],栈上面的顶点一定是刚才从顶点v递归调用时
进栈的,所以v一定能够到达那些顶点。
2 .dfs遍历时,如果已经遍历完v所能直接到达的顶点而low[v]=dfn[v],我们知道v一定能
到达栈里v上面的顶点,这些顶点的low一定小于 自己的dfn,不然就会出栈了,也不会
小于dfn[v],不然low [v]一定小于dfn[v],所以栈里v以其v以上的顶点组成的子图是一个强
连通分量,如果它不是极大强连通分量的话low[v]也一定小于dfn[v](这里不再详细说),
所以栈里v以其v以上的顶点组成的子图是一个极大强连通分量。
【时间复杂度】
因为所有的点都刚好进过一次栈,所有的边都访问的过一次,所以时间复杂度为O
(n+m)
核心代码(我用并查集实现的,冼貌似有不同,可以问他拿一下):
function getfather(dep:longint):longint;
begin
if father[dep]=dep then exit(dep);
getfather:=getfather(father[dep]);
father[dep]:=getfather;
end;
procedure dfs(dep:longint);
var
i,j,v2:longint;
begin
inc(time);
low[dep]:=time;
check[dep]:=true; zhan[dep]:=true;
for i:=1 to n do
begin
if map[dep,i]=1 then
begin
if not check[i] then dfs(i);
v2:=getfather(i);
if zhan[v2] then
if low[v2]
father[dep]:=v2;
end;
end;
end;
zhan[dep]:=false;
end;