众所周知,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. Linux文档整理之【Mysql安装与配置】

    最近公司让整理一个Linux安装Mysql的文档.所以就整理了一下,这里将自己整理的详细文档做个笔记. 1.下载Mysql. https://dev.mysql.com/downloads/mysql ...

  2. 前端开发HTML&css入门——伪类选择器和一些特殊的选择器

    伪类和伪元素 有时候,你需要选择本身没有标签,但是仍然易于识别的网页部位,比如段落首行或鼠标滑过的连接.CSS为他们提供一些选择器:伪类和伪元素. 常用的一些伪类选择器: :link :visited ...

  3. c# wpf 加密文本

    可以加密人们的内容,文本加密. 界面 <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation& ...

  4. SQL常见面试题-行列互换

      有一个SQL题在面试中出现的概率极高,最近有学生出去面试仍然会遇到这样的题目,在这里跟大家分享一下. 题目:数据库中有一张如下所示的表,表名为sales. 年 季度 销售量 1991 1 11 1 ...

  5. 安装运行redis

    在Linux系统上安装Redis 环境准备 Redis是C语言开发,建议在Linux上运行,本人系统centos-6.5. 安装redis需要先将官网下载的源码进行编译,编译依赖gcc环境,如果没有g ...

  6. slots_doc_call属性

    class Foo: "这时文档doc属性" __slots__ = ["name","age"] #[“name”=None,“age&q ...

  7. P3833 [SHOI2012]魔法树 (树链剖分模板题)

    题目链接:https://www.luogu.org/problem/P3833 题目大意:有一颗含有n个节点的树,初始时每个节点的值为0,有以下两种操作: 1.Add u v d表示将点u和v之间的 ...

  8. 2019 蓝桥杯国赛 B 组模拟赛 D. 程序设计:公约数

    蒜头君有n个数,他想要从中选出k个数,使得它们的最大公约数最大.请你求出这个最大的最大公约数. 输入格式第一行输入两个整数 .第二行输入 个整数 . 输出格式输出一个整数. 数据范围 样例输入14 3 ...

  9. 以太坊智能合约开发工具 Truffle 入门1

    Truffle是以太坊(Ethereum)智能合约开发的瑞士军刀,小巧好用,上手简单. 本篇文章主要展示如何用Truffle 开发第一个Ethereum智能合约. 1.准备工作:(本人针对window ...

  10. php内置函数分析之array_combine()

    PHP_FUNCTION(array_combine) { HashTable *values, *keys; uint32_t pos_values = ; zval *entry_keys, *e ...