众所周知,tarjan是个非常nb的人,他发明了很多nb的算法,tarjan算法就是其中一个,它常用于求解强连通分量,割点和桥等。虽然具体实现的细节不太一样,但是大体思路是差不多的。先来说一下大体思路。


 强连通分量,缩点

我们先来定义几个东西

时间戳:在搜索树中被遍历到的次序

比如在下图中

每个节点按照遍历顺序编的号就是它的时间戳

dfn[i]:表示第i个点的时间戳

low[i]:表示点i及i的子树所能追溯到的最早的节点的时间戳

low数组看起来很难理解是不是?

先来看一张非常经典的图

我们发现对于结点1,3,2,4,它们的low值都是1。为什么呢?因为这些点都直接或者间接的能够追溯到的最早的点1,而点1的dfn值为1,所以这些点的low值自然也就是1了

我们可以通过手算发现图中有三个强连通分量:{1,2,3,4},{5},{6}

我们发现,每一个连通分量都有一个点(以下称为代表点)的low值=dfn值,也就是说这个点及它的子树所能到达的最早的点就是他自己。

于是可以知道,对于dfn=low的点就是这一个强连通分量的代表点

那么要求强连通分量,实际上就是求有多少个点的low=dfn

用一个栈来实现,寻找low时只在栈里面找,弹出时不断从栈顶弹出直到弹出这个点

代码:

int dfn[],low[];
//dfn表示时间戳
//low表示点i及i的子树所能追溯到的最早的节点的时间戳
int ind;
//ind表示遍历顺序
int in[],s[],top;
//in表示当前这个点是否在队列中
//s是模拟的栈
//top是栈顶
int cnt_scc;
//强连通分量的个数
int scc[],cntscc[];
//scc表示每一个点属于哪一个强连通分量
//cntscc表示强连通分量的大小 void tarjan(int x)
{
dfn[x]=++ind;
low[x]=dfn[x];//初始化
s[top++]=x;//入栈
in[x]=;
for(int i=head[x];i;i=edg[i].nxt)
{
int v=edg[i].to;
if(!dfn[v])
//如果是没有遍历到的树边就先对它进行操作
{
tarjan(v);
low[x]=min(low[x],low[v]);//更新low值
}
else
{
if(in[v])//如果遍历过并且在栈中
//为什么一定要在栈中?
//因为如果不在栈中说明它已经属于其他强连通分量了
//而每一次出栈都会弹出完整的强连通分量,所以这个点肯定不会产生影响
{
low[x]=min(low[x],dfn[v]);
}
}
}
if(dfn[x]==low[x])//如果找到强连通分量的代表点
{
cnt_scc++;
while(s[top]!=x)//出栈
{
top--;
in[s[top]]=;
scc[s[top]]=cnt_scc;
cntscc[cnt_scc]++;
}
}
}

来看几道例题:

P2341 [HAOI2006]受欢迎的牛

如果有环,意味着这个环里的牛都互相喜欢

我们可以先求出环,然后把每一个环都看作一个点,这样整个图就变成了一个DAG(有向无环图)

看有几个点出度为0,如果大于一个点没有出边,就说明没有最受欢迎的牛,因为必定有一对牛相互不服

如果只有一个,那么强联通分量的大小就是答案

代码:

#include<bits/stdc++.h>
using namespace std; int n,m; int cnt,head[]; struct edge
{
int to,nxt;
}edg[]; inline void add(int from,int to)
{
edg[++cnt].to=to;
edg[cnt].nxt=head[from];
head[from]=cnt;
} int dfn[],low[],ind,in[];
int s[],top;
int cnt_scc;
int scc[],cntscc[]; void tarjan(int x)
{
dfn[x]=++ind;
low[x]=dfn[x];
s[top++]=x;
in[x]=;
for(int i=head[x];i;i=edg[i].nxt)
{
int v=edg[i].to;
if(!dfn[v])
{
tarjan(v);
low[x]=min(low[x],low[v]);
}
else
{
if(in[v])
{
low[x]=min(low[x],dfn[v]);
}
}
}
if(dfn[x]==low[x])
{
cnt_scc++;
while(s[top]!=x)
{
top--;
in[s[top]]=;
scc[s[top]]=cnt_scc;
cntscc[cnt_scc]++;
}
}
} int out[];
int ans; int main()
{
scanf("%d%d",&n,&m);
for(int i=,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
}
for(int i=;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
for(int i=;i<=n;i++)
{
for(int j=head[i];j;j=edg[j].nxt)
{
int k=edg[j].to;
if(scc[i]!=scc[k]) out[scc[i]]++;
}
}
for(int i=;i<=cnt_scc;i++)
{
if(!out[i])
{
if(!ans)
ans=i;
else
{
cout<<;
return ;
}
}
}
cout<<cntscc[ans];
}

P2002 消息传递

我们发现如果这个题有环,那么不论在这个环上哪一个点开始传递信息,这个环中其他的点都可以到达,那么可以用tarjan把环缩成点。为了使每一个点都能被传递到,只需要找到所有入度为0的点,在这些点上开始传递信息就好了

代码:

#include<bits/stdc++.h>
using namespace std; int n,m; int head[],cnt;
struct edge
{
int to,nxt;
}edg[]; inline void add(int from,int to)
{
edg[++cnt].to=to;
edg[cnt].nxt=head[from];
head[from]=cnt;
} int low[],dfn[],ind;
int s[],top;
bool in[];
int scc[],cnt_scc; inline void tarjan(int x)
{
dfn[x]=++ind;
low[x]=dfn[x];
in[x]=;
s[top++]=x;
for(int i=head[x];i;i=edg[i].nxt)
{
int v=edg[i].to;
if(!dfn[v])
{
tarjan(v);
low[x]=min(low[x],low[v]);
}
else
{
if(in[v])
low[x]=min(low[x],dfn[v]);
}
}
if(low[x]==dfn[x])
{
cnt_scc++;
while(s[top]!=x)
{
in[s[--top]]=;
scc[s[top]]=cnt_scc;
}
}
} int ans;
int gin[]; int main()
{
cin>>n>>m;
for(int i=;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
if(x!=y)
add(x,y);
}
for(int i=;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
for(int i=;i<=n;i++)
{
for(int j=head[i];j;j=edg[j].nxt)
{
int v=edg[j].to;
if(scc[v]!=scc[i])
{
gin[scc[v]]++;
}
}
}
for(int i=;i<=cnt_scc;i++)
{
if(!gin[i]) ans++;
}
cout<<ans;
}

类似的题还有洛谷1262,这里就先不说了


tarjan求割点

什么是割点?

给你一张连通图,在上面找一个点,如果去掉这个点和所有连着它的边,整个图就不能保持连通,那么这个点就是割点

比如这张图,里面的割点有1,4,5

怎么求割点?

首先选定一个dfs树的树根,从这个点开始遍历整张图。

对于根节点,判断是不是割点显然只需要看他的子树的个数是不是大于等于2

对于非根节点x,如果存在儿子节点y,使得dfn[x]<=low[y],则x一定是割点。

显然如果x的所有儿子能够不经过x直接到达他的祖先,这个点就一定不是割点;反之,则说明去掉它一定会改变图的连通性

代码:

int low[],dfn[],ind,ans;
bool cut[]; inline void tarjan(int x,int fa)
{
dfn[x]=++ind;
low[x]=dfn[x];
int ch=;
for(int i=head[x];i;i=edg[i].nxt)
{
int v=edg[i].to;
if(!dfn[v])
{
tarjan(v,fa);
low[x]=min(low[x],low[v]);
if(low[v]>=dfn[x]&&x!=fa) cut[x]=;
if(x==fa) ch++;
}
else
{
low[x]=min(low[x],dfn[v]);
}
}
if(x==fa&&ch>=) cut[fa]=;
}

例:

P3388 【模板】割点(割顶)

代码:
#include<bits/stdc++.h>
using namespace std; int n,m; int head[],cnt;
struct edge
{
int to,nxt;
}edg[]; inline void add(int from,int to)
{
edg[++cnt].to=to;
edg[cnt].nxt=head[from];
head[from]=cnt;
} int low[],dfn[],ind,ans;
bool cut[]; inline void tarjan(int x,int fa)
{
dfn[x]=++ind;
low[x]=dfn[x];
int ch=;
for(int i=head[x];i;i=edg[i].nxt)
{
int v=edg[i].to;
if(!dfn[v])
{
tarjan(v,fa);
low[x]=min(low[x],low[v]);
if(low[v]>=dfn[x]&&x!=fa) cut[x]=;
if(x==fa) ch++;
}
else
{
low[x]=min(low[x],dfn[v]);
}
}
if(x==fa&&ch>=) cut[fa]=;
} int main()
{
cin>>n>>m;
for(int i=;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
for(int i=;i<=n;i++)
{
if(!dfn[i]) tarjan(i,i);
}
for(int i=;i<=n;i++)
{
if(cut[i]==) ans++;
}
cout<<ans<<endl;
for(int i=;i<=n;i++)
{
if(cut[i])
printf("%d ",i);
}
}
 

Tarjan算法整理的更多相关文章

  1. Tarjan 算法 自学整理

    算法介绍 如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量( ...

  2. 求图的强连通分量--tarjan算法

    一:tarjan算法详解 ◦思想: ◦ ◦做一遍DFS,用dfn[i]表示编号为i的节点在DFS过程中的访问序号(也可以叫做开始时间)用low[i]表示i节点DFS过程中i的下方节点所能到达的开始时间 ...

  3. 割点(Tarjan算法)【转载】

    本文转自:www.cnblogs.com/collectionne/p/6847240.html 供大家学习 前言:之前翻译过一篇英文的关于割点的文章(英文原文.翻译),但是自己还有一些不明白的地方, ...

  4. Tarjan算法及其应用

    Tarjan算法及其应用 引入 tarjan算法可以在图上求解LCA,强连通分量,双联通分量(点双,边双),割点,割边,等各种问题. 这里简单整理一下tarjan算法的几个应用. LCA http:/ ...

  5. 割点(Tarjan算法)

    本文可转载,转载请注明出处:www.cnblogs.com/collectionne/p/6847240.html .本文未完,如果不在博客园(cnblogs)发现此文章,请访问以上链接查看最新文章. ...

  6. Tarjan算法分解强连通分量(附详细参考文章)

    Tarjan算法分解强连通分量 算法思路: 算法通过dfs遍历整个连通分量,并在遍历过程中给每个点打上两个记号:一个是时间戳,即首次访问到节点i的时刻,另一个是节点u的某一个祖先被访问的最早时刻. 时 ...

  7. 20行代码实现,使用Tarjan算法求解强连通分量

    今天是算法数据结构专题的第36篇文章,我们一起来继续聊聊强连通分量分解的算法. 在上一篇文章当中我们分享了强连通分量分解的一个经典算法Kosaraju算法,它的核心原理是通过将图翻转,以及两次递归来实 ...

  8. 算法学习笔记:Tarjan算法

    在上一篇文章当中我们分享了强连通分量分解的一个经典算法Kosaraju算法,它的核心原理是通过将图翻转,以及两次递归来实现.今天介绍的算法名叫Tarjan,同样是一个很奇怪的名字,奇怪就对了,这也是以 ...

  9. 浅谈 Tarjan 算法之强连通分量(危

    引子 果然老师们都只看标签拉题... 2020.8.19新初二的题集中出现了一道题目(现已除名),叫做Running In The Sky. OJ上叫绮丽的天空 发现需要处理环,然后通过一些神奇的渠道 ...

随机推荐

  1. Highways POJ-1751 最小生成树 Prim算法

    Highways POJ-1751 最小生成树 Prim算法 题意 有一个N个城市M条路的无向图,给你N个城市的坐标,然后现在该无向图已经有M条边了,问你还需要添加总长为多少的边能使得该无向图连通.输 ...

  2. 洛谷 P5660 数字游戏 & [NOIP2019普及组]

    传送门 洛谷改域名了QAQ 解题思路 没什么好说的,一道红题,本不想发这篇博客 ,但还是尊重一下CCF吧QAQ,怎么说也是第一年CSP呢! 用getchar一个个读入.判断.累加,最后输出即可. 不过 ...

  3. 理解 JavaScript 闭包

    这是本系列的第 4 篇文章. 作为 JS 初学者,第一次接触闭包的概念是因为写出了类似下面的代码: for (var i = 0; i < helpText.length; i++) { var ...

  4. Codeforces - 1202D - Print a 1337-string... - 构造

    https://codeforces.com/contest/1202/problem/D 当时想的构造是中间两个3,然后前后的1和7组合出n,问题就是n假如是有一个比较大的质数因子或者它本身就是质数 ...

  5. 发布一本用 GitBook 编辑的书

    在上一篇的文章里,我们已经写好了一本名叫 erdong-first-book 的书,但是在本地浏览很不方便,我们希望放到网络上,可以随时.方便的访问这个书籍.这个需求可以使用多种方式来实现,比如第一种 ...

  6. JS中对数组元素进行增、删、改、查的方法,以及其他方法

    前言 昨天联调一个页面,看着就一个页面,接口倒是不少. 热点问题配置测试联调完成(同步异步接口共11个) 1.配置新增 2.配置编辑 3.配置删除 4.热点问题新增 5.热点问题编辑 6.热点问题删除 ...

  7. Java基础学习(1)

    Java基础知识 Java平台 1995年由Sun公司创建 Java的体系结构 JVM Java Virtue Machine Java代码的执行顺序 JDK Java Development Kit ...

  8. linux c下的c文件 h文件 o文件 so文件 a文件 可执行文件 gcc使用

    linux下c语言工程: c文件:主要每个模块的原代码都在c文件中. h文件:每个c文件都跟着一个h文件,h文件的作用是放着c文件中函数的声明,结构体的定义,宏的定义等. o文件:目标文件.每个文件经 ...

  9. java 通过反射获取数组

    1.创建数组.设置数组元素.访问数组 一维数组: 多维数组: public Class<?> getComponentType() 返回表示数组组件类型的 Class.如果此类不表示数组类 ...

  10. LOJ6279 果树

    我丢 之前sun在某校集训给我看过 当时也没想起来 今天补省集的锅的时候发现 wok这题我还听过?! 身败名裂.jpg (可是你记性不好这事情不已经人尽皆知了吗? 咳咳 回归正题 考虑对于两个同色的点 ...