浅谈Tarjan求强连通分量
首先,要知道什么是强连通分量,这个还是比较简单的吧。
那么就谈谈Tarjan求强连通分量。
概率(扯淡)
首先,Tarjan算法是基于对图深度优先搜索(DFS)的算法,每个强连通分量为搜索树中的一个子树。
搜索时把当前搜索树中未处理的结点加入一个栈,回溯时可以判断栈顶中的结点是否构成一个强连通分量。
DFS的过程中遇到的四种边:
1、树枝边:DFS时经过的边,即DFS搜索树上的边。
2、前向边:与DFS方向一致,从某个结点指向其某个子孙的边
3、后向边:与DFS方向相反,从某个结点指向其某个祖先的边
4、横叉边:从某个结点指向搜索树中另一个子树中的某个结点的边。
定义DFN(n)为结点u的搜索次序编号(时间戳)可以理解为是第几个dfs到的,low(u)为 u 或u的子树能够追溯到的最早的栈中结点的DFN值。其定义可以得出:
如果(u,v)为树枝边,u 为 v 的父节点,则low( u ) = min(low( u ), low( v ) );
如果(u,v)为后向边或指向栈中结点的横叉边,则low( u ) = min (low( u ),dfn( v ));
然后如果在回溯的时候DFN[u]=low[u] 那么久可以退栈了,就是以u为根的搜索子树上所有还在栈中的结点是一个强连通分量,就是u和在u之后搜索到的可以组成一个强连通分量。
算法流程(乱搞+魔改)
就是先一个for枚举每个点为起点(搜索树的树根)看当前是否被搜索过,没有就进去乱搞一通,然后就可以了(就是利用上面的那两个得出来的东东)
我扔:https://www.luogu.org/problemnew/show/P1726
这题接近于裸题,就多记一个数量就行了
代码:
#include<bits/stdc++.h>
#define N 100050
using namespace std;
struct edge{//链式前向星
int v,next;
}e[N];
int p[N],eid;
void add(int u,int v){
eid++;
e[eid].v=v;
e[eid].next=p[u];
p[u]=eid;
}
int dfn[N],low[N],bel[N],sta[N],bl[N],sz,tot,top;
void dfs(int x){ //Tarjan 求强连通分量
dfn[x]=++sz;
low[x]=dfn[x]; //一开始low[x]就是等于dfn[x](暂时追溯到最早的就是自己)
sta[++top]=x;
for(int i=p[x];i;i=e[i].next){
int v=e[i].v;
if(!dfn[v]){ //第一个得出来的
dfs(v);
low[x]=min(low[x],low[v]);
}
else
if(!bel[v]) low[x]=min(low[x],dfn[v]);
}
if(low[x]==dfn[x]){
bel[x]=++tot;
bl[tot]++; //记一下强连通分量的大小
while(sta[top]!=x){
bel[sta[top]]=tot;
--top;
bl[tot]++;
}
--top;
}
}
int n,m;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v,t;
scanf("%d%d%d",&u,&v,&t);
if(t==2) add(v,u);
add(u,v);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) dfs(i);
}
int ma=0,mai=0;
for(int i=1;i<=tot;i++){
if(bl[i]>ma) ma=bl[i],mai=i;
}
for(int i=1;i<=n;i++){
if(ma==bl[bel[i]]){
mai=bel[i];
break;
}
}
printf("%d\n",ma);
for(int i=1;i<=n;i++){
if(bel[i]==mai) printf("%d ",i);
}
return 0;
}
//啦啦啦啦(防伪标识)
蛤?就没了
没了……
参考资料:
《信息学奥赛一本通·提高篇》