tarjan求有向图强连通分量(scc)
【一. 常见概念理解】
- 强连通分量:任意两个节点相互可达的强连通图中最大的一个。
- tarjan:一个类似dfs的过程,用于寻找有向图中的强连通分量。
- 缩点:对于具有传导性的关系,把有向图中的"环"缩成点,形成有向无环图。
求强连通分量:先把有向图看成一棵有向树,进行dfs(tarjan)。
当有节点的连边连向dfn比它小的地方时,就形成了环,需要判断连通分量了。
【二. 常见数组及其使用】
(1)、dfn[i],表示时间戳,即这个点在dfs时是第几个被搜到的。
(2)、low[i],表示[这个点以及其子孙节点]连的所有点中[dfn最小的值],
即:能匹配到的最上层的祖先的位置。
(3)、stack[i],表示当前过程中可能构成强连通分量的所有点。
(4)、vis[i],表示一个点是否在stack[i]数组中。
【三. tarjan算法的实现过程】
从点u开始遍历(某个dfn为0的节点):
(1)首先初始化:dfn[u]=low[u]=第几个被dfs到。
(2)将u存入stack[]中,并将vis[u]设为true。
--- stack[ ]的意义是:如果u在stack中,在u被回溯时、
u和栈中所有在它之后的点、都可以构成强连通分量。
(3)遍历u的所有连向点v,如果dfn[v]为0,即未访问过,就对点v进行dfs。
在遍历子节点的连向边的过程中,如果遍历到dfn比它小的地方,
判断该连向节点是否在stack[ ]里,如果在,更新 low[v]。
low[u]=min{low[u],low[v]};
--- low[ u ]的意义是:记录该点能连通到的最大的祖先节点(或者自己),
即能连通到的dfn最小的值,且该值在栈内(保证是u的祖先)。
(4)如果dfn[u]=low[u],说明 [ u点及u点之下的所有子节点 ] 没有边会指向u的祖先,
那么u就是强连通分量的顶端,记录此次搜索到的强连通分量。
把所有的u点及以后压入栈中的所有点弹出,vis[ ]改为false(或打上相同标记)。
【四. 常见例题分析】
【例题1】【p2341】受欢迎的牛
- 告诉你有n头牛,m个崇拜关系,并且崇拜具有传递性,
- 如果a崇拜b,b崇拜c,则a崇拜c,求最后有几头牛被所有牛崇拜。
【标签】有向图缩点 + 连通性判断 + 入度出度的判断
将整张图(上的强连通分量)进行缩点,剩下的点都不强连通,整张图变成了有向无环图。
有向无环图上必然有>=1个的点出度为0,如果>1个,那么图不相连、答案为0。
如果只有一个点出度为0,判断它是不是强连通分量缩成的点,输出包含的点数。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
/*【p2341】受欢迎的牛
告诉你有n头牛,m个崇拜关系,并且崇拜具有传递性,
如果a崇拜b,b崇拜c,则a崇拜c,求最后有几头牛被所有牛崇拜。*/
//将整张图(上的强连通分量)进行缩点,剩下的点都不强连通,整张图变成了有向无环图。
//有向无环图上必然有>=1个的点出度为0,如果>1个,那么图不相连、答案为0。
//如果只有一个点出度为0,判断它是不是强连通分量缩成的点,输出包含的点数。
const int N=500019;
ll n,m,x,y,tot=0,head[N*2],tmp=0,ans=0;
ll dfss=0,num=0,sum=0,col[N],times[N],du[N];
ll dfn[N],low[N],stack[N],vis[N]; //tarjan算法中的四个数组
struct node{ ll ver,nextt; }e[N*2];
void add(ll u,ll v){ e[++tot].ver=v,e[tot].nextt=head[u],head[u]=tot; }
void tarjan(ll u){ //dfss记录当前dfs序到达的数字
dfn[u]=low[u]=++dfss,vis[u]=1,stack[++num]=u; //步骤一:初始化
for(int i=head[u];i;i=e[i].nextt){ //步骤二:枚举连向点,递归更新
if(!dfn[e[i].ver]) tarjan(e[i].ver),low[u]=min(low[u],low[e[i].ver]);
else if(vis[e[i].ver]) low[u]=min(low[u],dfn[e[i].ver]); //这里写dfn或low都可以
} //↑↑步骤三:已经到达过,判断是否在当前栈内(栈内都是当前情况下能相连的点)
if(dfn[u]==low[u]){
col[u]=++sum; vis[u]=0;
while(stack[num]!=u){ //u上方的节点是可以保留的
col[stack[num]]=sum;
vis[stack[num]]=0,num--;
} num--; //col数组记录每个点所在连通块的编号
}
}
int main(){
while(scanf("%lld%lld",&n,&m)!=EOF){
memset(vis,false,sizeof(vis));
memset(dfn,0,sizeof(dfn));
dfss=0,num=0,tot=0,sum=0,ans=0,tmp=0;
memset(head,0,sizeof(head));
for(ll i=1;i<=m;i++){
scanf("%lld%lld",&x,&y),add(x,y);
} for(ll i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
for(ll u=1;u<=n;u++){
for(ll i=head[u];i;i=e[i].nextt)
if(col[e[i].ver]!=col[u]) du[col[u]]++;
times[col[u]]++; //记录强连通分量大小
} for(ll i=1;i<=sum;i++) //对每种颜色(缩点得到的点)
if(du[i]==0) tmp++,ans=times[i];
if(tmp==0||tmp>1) cout<<0<<endl;
else cout<<ans<<endl;
}
}
【例题2】【p2194】HXY烧情侣
- n个点,m条有向边,每个点都有点权w[i]。
- 如果能从一个点