变量解释:

low 指当前节点在同一强连通分量(或环)能回溯到的dfn最小的节点

dfn 指当前节点是第几个被搜到的节点(时间戳)

sta 栈

vis 是否在栈中

ans 指强连通分量的数量

top 栈顶

1.求强连通分量

定义:如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

算法:在有向图中从一点(u)开始dfs,记录dfn,搜到一个已在栈中的点(v)时用dfn[v] (low[v]也行,但只有求强连通分量时可以别的只能用dfn[v]) 尝试更新low[u],并在回溯时更新沿路的点的low值,走到low值与dfn相同的点时记录这个强连通分量即可。

也就是说:在同一个强连通分量中所有点low值相同,也就是有一个代表点(代表点即所有点的low值即强连通分量中dfn值最小的点)

时间复杂度为O(E+V)

code

void tarjan(int u){
dfn[u]=low[u]=++cnt;//初始化一点的dfn和low
sta[++top]=u,vis[u]=true;//入栈
for(int i=head[u];i;i=edge[i].next){//邻接表
int v=edge[i].to;
if(!dfn[v]){//如果没走过
tarjan(v);
low[u]=min(low[u],low[v]);//回溯过程时low值传递
}
else if(vis[v]) low[u]=min(low[u],dfn[v]); //low[v]也行 用代表点更新
}
if(dfn[u]==low[u]) {//如果是代表点 记录并出栈
ans++;//记录强连通分量个数
while(sta[top]!=u){
vis[sta[top]]=false;
top--;
}
vis[sta[top]]=false;
top--;
}
return ;
}

2.求无向图的割点与割边

割点:在无向图中,如果将一个点以及所有连接该点的边都去掉,图就不再连通,那么这个点就叫做这个图的一个割点。

割边:在无向图中,如果将一条边去掉,图就不再连通则称这条边为图的一个割边。

求割点:如果一个点(u)所连接的几个节点(v)的low值大或等于此节点(u)的dfn值时说明之后的节点(v)无法连接到比此点(u)更早的点上,则说明这个节点(u)是一个割点。PS:根节点需特判,当根节点在dfs树有两个或更多个子树时则说明根节点是割点

求割边:与割点类似,如果一个点(u)的dfn值大于(不能等于,否则不一定)和它连接的一个节点(v)的low值,则说明这条边(uv)为图的一个割边

变量解释:

sum 指总共有几个割点(边)

割点code

void cutpoint(int u){
int fl=0;//为特判准备
dfn[u]=low[u]=++cnt;//初始化
for(int i=head[u];i;i=edge[i].next){//用邻接表,下同
int v=edge[i].to;
if(!dfn[v]){
cutpoint(v);
low[u]=min(low[u],low[v]);
if(u!=root&&low[v]>=dfn[u]&&!cpoint[u]) sum++,cpoint[u]=1;//不是根节点&&v的low值>=u的dfn值&&此点没有算过
if(u==root) fl++;//此时特判++
}
low[u]=min(low[u],dfn[v]);
}
if(fl>=2&&!cpoint[u]) sum++,cpoint[u]=1;//根节点若有两棵子树则是割点
}

割边code

void cutedge(int u,int f){
dfn[u]=low[u]=++cnt;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(!dfn[v]){
cutedge(v,u);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]) cedge[++sum]=i;//记录边的序号
}
else if(v!=f) low[u]=min(low[u],dfn[v]); //只有当v不是u的上一个节点时可行
}
}

完整模板code:

ps:这里就不打注释了,核心就在上面的部分里

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std; const int MAX=1000010;
int n,m,cnt,sum,root;
int head[MAX],low[MAX],dfn[MAX],cpoint[MAX],cedge[MAX]; struct edg{
int to,next,from;
}edge[MAX]; void add(int x,int y){
edge[++cnt].next=head[x];
edge[cnt].from=x,edge[cnt].to=y;
head[x]=cnt;
} void cutpoint(int u){
int fl=0;
dfn[u]=low[u]=++cnt;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(!dfn[v]){
cutpoint(v);
low[u]=min(low[u],low[v]);
if(u!=root&&low[v]>=dfn[u]&&!cpoint[u]) sum++,cpoint[u]=1;
if(u==root) fl++;
}
low[u]=min(low[u],dfn[v]);
}
if(fl>=2&&!cpoint[u]) sum++,cpoint[u]=1;
} void cutedge(int u,int f){
dfn[u]=low[u]=++cnt;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(!dfn[v]){
cutedge(v,u);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]) cedge[++sum]=i;
}
else if(v!=f) low[u]=min(low[u],dfn[v]);
}
} void mset(){
memset(dfn,0,sizeof dfn);
memset(low,0,sizeof low);
cnt=sum=0;
} void find_cutpoint(){
for(int i=1;i<=n;i++) if(!dfn[i]) {
root=i;
cutpoint(i);
}
printf("%d\n",sum);
for(int i=1;i<=n;i++) if(cpoint[i]) printf("%d ",i);
} void find_cutedge(){
for(int i=1;i<=n;i++) if(!dfn[i]) cutedge(i,0);
printf("%d\n",sum);
for(int i=1;i<=sum;i++) printf("%d %d\n",edge[cedge[i]].from,edge[cedge[i]].to);
} int main(){
// freopen("testdata.txt","r",stdin);
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int a,b;
scanf("%d %d",&a,&b);
add(a,b);
add(b,a);
}
find_cutedge();
mset();
find_cutpoint();
return 0;
}

Tarjan算法 (强联通分量 割点 割边)的更多相关文章

  1. tarjan模板 强联通分量+割点+割边

    // https://www.cnblogs.com/stxy-ferryman/p/7779347.html ; struct EDGE { int to, nt; }e[N*N]; int hea ...

  2. Tarjan算法---强联通分量

    1.基础知识 在有向图G,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子 ...

  3. tarjan求强联通分量

    tarjan求强联通分量 变量含义说明: pre[i]:i点的被访问的时钟编号,被分配后保持不变 low[i]:i点能访问的最先的点的时钟编号,随子节点改变 scc_no[i]:i点所在的强联通分量的 ...

  4. Kosaraju算法---强联通分量

    1.基础知识 所需结构:原图.反向图(若在原图中存在vi到vj有向边,在反向图中就变为vj到vi的有向边).标记数组(标记是否遍历过).一个栈(或记录顶点离开时间的数组).      算法描叙: :对 ...

  5. Tarjan的强联通分量

    求强联通分量有很多种. <C++信息学奥赛一本通>  中讲过一个dfs求强联通分量的算法Kosdaraju,为了骗字数我就待会简单的说说.然而我们这篇文章的主体是Tarjan,所以我肯定说 ...

  6. Tarjan求强联通分量+缩点

    提到Tarjan算法就不得不提一提Tarjan这位老人家 Robert Tarjan,计算机科学家,以LCA.强连通分量等算法闻名.他拥有丰富的商业工作经验,1985年开始任教于普林斯顿大学.Tarj ...

  7. USACO06JAN The Cow Prom /// tarjan求强联通分量 oj24219

    题目大意: n个点 m条边的图 求大小大于1的强联通分量的个数 https://www.cnblogs.com/stxy-ferryman/p/7779347.html tarjan求完强联通分量并染 ...

  8. tarjan求强联通分量 模板

    void tarjan(int u) { dfn[u]=low[u]=++dfs_clock; stack_push(u); for (int c=head[u];c;c=nxt[c]) { int ...

  9. 培训补坑(day2:割点与桥+强联通分量)

    补坑ing... 好吧,这是第二天. 这一天我们主要围绕的就是一个人:tarjan......创造的强联通分量算法 对于这一天的内容我不按照顺序来讲,我们先讲一讲强联通分量,然后再讲割点与桥会便于理解 ...

随机推荐

  1. 【ACM】nyoj_2_括号配对问题_201308091548

    括号配对问题时间限制:3000 ms  |  内存限制:65535 KB 难度:3描述 现在,有一行括号序列,请你检查这行括号是否配对. 输入 第一行输入一个数N(0<N<=100),表示 ...

  2. [bzoj4659\2694]Lcm_数论_莫比乌斯反演

    Lcm bzoj-4659 bzoj-2694 题目大意:给出A,B,考虑所有满足l<=a<=A,l<=b<=B,且不存在n>1使得n^2同时整除a和b的有序数对(a,b ...

  3. java5核心基础之泛型(3)-泛型作用于编译阶段-怎样将String对象传入Integer类型的泛型对象中?

    泛型作用于编译阶段: 泛型是作用于编译阶段,在编译阶段控制类型,以确保在编写代码的时候仅仅能传入指定类型数据到泛型集合对象中去. 怎样验证呢,贴代码例如以下: package highBasic.ge ...

  4. 开源 免费 java CMS - FreeCMS2.0 会员password设置

    项目地址:http://www.freeteam.cn/ password设置 从右側管理菜单点击password设置进入.   输入正确的当前password和新password后点击改动就可以.

  5. 18005 It is not ugly number

    18005 It is not ugly number 时间限制:2000MS  内存限制:65535K提交次数:0 通过次数:0 题型: 编程题   语言: G++;GCC Description ...

  6. C#替换字符串起始/结尾指定的字符串

    #region 替换字符串起始位置(开头)中指定的字符串 /// <summary> /// 替换字符串起始位置(开头)中指定的字符串 /// </summary> /// & ...

  7. 数据挖掘十大经典算法--CART: 分类与回归树

    一.决策树的类型  在数据挖掘中,决策树主要有两种类型: 分类树 的输出是样本的类标. 回归树 的输出是一个实数 (比如房子的价格,病人呆在医院的时间等). 术语分类和回归树 (CART) 包括了上述 ...

  8. 使用java -jar运行jar包

    使用java -jar运行jar包 摘要 其实不难,网上搜了不少,但是讲不全,或者讲得不少,有的细节却漏掉了, 在此,我想将详细点 java -jar 1.其实命令比较简单:进入相应的目录后,执行ja ...

  9. POJ3349 Language: Snowflake Snow Snowflakes

    POJ3349 Language: Snowflake Snow Snowflakes 题目:传送门 题解: 链表+hash的一道水题 填个坑补个漏... 代码: #include<cstdio ...

  10. Linux - 设置光盘,开机自动挂载。

    设置光盘,开机自动挂载. 挂载, 在linux操作系统中, 挂载是指将一个设备(通常是存储设备)挂接到一个已存在的目录上. 我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的目录上, ...