// 此博文为迁移而来,写于2015年4月14日,不代表本人现在的观点与看法。原始地址:http://blog.sina.com.cn/s/blog_6022c4720102vxnx.html

UPDATE(20180809):对代码和描述进行大量修改。

UPDATE(20151104):新增Tarjan算法核心代码。

1、前言
       我始终记得去年冬天有天吃完饭后,我们在买东西的时候讨论着强连通分量和Tarjan什么的。当时我真的什么都没听懂啊。。。什么强连通图,强连通分量,极大强连通分量。。。当然现在还是知道了。
       
2、概念
       Tarjan算法,由Tarjan发明。作用在于求图中的强连通分量。什么是强连通?在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。          
       先举一个很简单的例子,下图中,子图{1,2,3,4}中的节点两两可达,所以子图{1,2,3,4}为其强连通分量。
 

求强连通分量的话,除了枚举什么的,还有两种O(N+M)的方法,Kosaraju算法或Tarjan算法。今天介绍Tarjan算法。
 
3、求强连通分量
       由于我们跑的肯定是有向图,所以其实我们可以把每个强连通分量看成一棵子树。首先要定义两个数组:dfn(u)为节点u搜索的次序编号(时间戳,并不是深度),low(u)为u或u的子树能够追溯到的最早的栈中节点的次序编号。于是存在一个定理(也是最核心的判断方法):当dfn(u)=low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。Tarjan是一个图上深度优先搜索的算法,下面为以上图为样本的具体操作步骤:
 
(1)第一次递归,将1,2,5,6全部加入栈中。到终点节点6时,发现dfn[6]=low[6]=4,即{6}是一个强连通分量。
       (UPDATE:图中有一处错误,low[5]=3而不是1,特此说明)
(2)回溯后,同样可以得到dfn[5]=low[5],即(5}是一个强连通分量。(图略)
 
(3)再次回溯,由节点2搜索到3,将3加入栈中,发现3存在一条子边连向已经在栈中的节点1,如紫色边所示,则可得到low[3]=dfn[1]=1。另一条边连向节点6,但是6没有在栈中,则不管了。。

(4)返回节点2之后,我们可得low[2]=low[3]=1。(图略)
(5)返回节点1之后,搜索到节点4,存在一条边与在栈中的3相连,则low[4]=dfn[3]=5。至此,搜索结束,返回节点1,发现dfn[1]=low[1]=1,则在栈中的{1,2,3,4}组成一个强连通分量。Tarjan算法结束。

综上所述,求得的三个强连通分量为:{1,2,3,4},{5},{6}。
 
代码:
#include <cstdio>

#define MAXN 1005
#define MAXM 10005 int o, n, m, u, v, h[MAXN], ins[MAXN], dfn[MAXN], low[MAXN], tim, st[MAXN << ], t, tot, top[MAXN], ans[MAXN][MAXN]; struct Edge {
int v, next;
} e[MAXM]; int min(int a, int b) {
return a < b ? a : b;
} void add(int u, int v) {
o++, e[o] = (Edge) {v, h[u]}, h[u] = o;
} void tarjan(int o) {
dfn[o] = low[o] = ++tim, ins[o] = , st[++t] = o;
for (int x = h[o]; x; x = e[x].next) {
int v = e[x].v;
if (!dfn[v]) tarjan(v), low[o] = min(low[o], low[v]);
else if (ins[v] && dfn[v] < low[o]) low[o] = dfn[v];
}
if (dfn[o] == low[o]) {
int x; tot++;
do {
x = st[t--];
ins[x] = ;
ans[tot][++top[tot]] = x;
} while (x != o);
}
} int main() {
freopen("tarjan.in", "r", stdin);
freopen("tarjan.out", "w", stdout);
scanf("%d %d", &n, &m);
for (int i = ; i <= m; i++) scanf("%d %d", &u, &v), add(u, v);
for (int i = ; i <= n; i++) if (!dfn[i]) tarjan(i);
for (int i = ; i <= tot; i++) {
for (int j = ; j <= top[i]; j++) printf("%d ", ans[i][j]);
printf("\n");
}
return ;
}
 
4、缩点
       其实上面,Tarjan算法本身已经讲完,但是,强连通分量求了肯定不是用来玩的。后面会给出一道许运用到Tarjan+缩点的题目,现在先讲概念。缩点的意思很简单,将一个强连通分量缩成一个点。作用不言而喻:如果题目所给的图存在环,还可以走重复的路,同时又要你求出权值和最大,怎么办?将强连通分量缩成点,接下来的任务就很简单了。在进行Tarjan求强连通分量的时候,我们就可以提前处理好一些内容,如得出缩点后的节点数,以及每个节点所属的强连通分量。
       缩点的过程有两种方式,根据情况可以选择:
  (1)双图法
    缩点后的节点全部重新存在新的图中。该方法的空间需求较大,但是一点都不麻烦。
  (2)新节点法
    如果原图存在n个节点,求出了k个非一个节点的强连通分量,则新加k个节点,共(n+k)个节点。在处理第i个强连通分量的时候,将所有与i中节点相连的边连到新的节点(n+i),同时对强连通分量中的节点全部进行标记(对边标记也可以),下次搜索的时候不可进行访问。
5、例题
 
抢掠计划  [APIO 2009]
       S城中的道路都是单向的。不同的道路由路口连接。按照法律的规定, 在每个路口都设立了一个 S 银行的 ATM 取款缩机。令人奇怪的是,S 的酒吧也都设在路口,虽然并不是每个路口都设有酒吧。 B 计划实施 S 有史以来最惊天动地的 ATM 抢劫。他将从市中心 出发,沿着单向道路行驶,抢劫所有他途径的 ATM 机,最终他将在一个酒吧庆祝他的胜利。 使用高超的黑客技术,他获知了每个 ATM 机中可以掠取的现金数额。他希 望你帮助他计计算从市中心出发最后到达某个酒吧时最多能抢劫的现金总数。他可 以经过同一路口或道路任意多次。但只要他抢劫过某个 ATM 机后,该 ATM 机 里面就不会再有钱了。 例如,假设该城中有 6 个路口,道路的连接情况去网上找吧。
 
市中心在路口 1,由一个入口符号→来标识,那些有酒吧的路口用双圈来表示。每个 ATM 机中可取的钱数标在了路口的上方。在这个例子中,B 能抢劫的现金总数为 47,实施的抢劫路线是:1-2-4-1-2-3-5。 
 
输入格式 
第一行包含两个整数 n、m。n 表示路口的个数,m 表示道路条数。接下来 m 行,每行两个整数,这两个整数都在 1 到 n 之间,第 i+1 行的两个整数表示第 i 条道路的起点和终点的路口编号。接下来 n 行,每行一个整数,按顺序表示每 个路口处的 ATM 机中的钱数。接下来一行包含两个整数 s、p,s 表示市中心的 编号,也就是出发的路口。p表示酒吧数目。接下来的一行中有 p 个整数,表示 p 个有酒吧的路口的编号。 
 
输出格式 
输出一个整数,表示 B 从市中心开始到某个酒吧结束所能抢劫的最多的现金总数。 
 
数据范围 
50%的输入保证 n, m<=3000。所有的输入保证n, m<=500000。每个 ATM 机中可取的钱数为一个非负整数且不超过 4000。输入数据保证你可以从市中心沿着 s 的单向的道路到达其中的至少一个酒吧。 
 
输入样例 
6 7 
1 2 
2 3 
3 5 
2 4 
4 1 
2 6 
6 5 
10 12 8 16 1 5 
1 4 
4 3 5 6 
 
输出样例 
47 
 
       主要到图中没有边权而有点权,且要求点权综合越大越好。就样例而言,出现的环对结果没有影响,但搜索的时候很难处理,延伸到所有强连通分量其实均可看为一点,故可使用Tarjan算法求出所有强连通分量,并进行缩点。这里采用的是上述的双图法,直接将所求的的所有强连通分量放进一个新图中。转化时注意一些细节即可。
  但由于本题数据较大,n/m <= 500000,爆栈感觉是件轻而易举的事情。这里仅提供较朴素的Tarjan+搜索算法,得分率为80%,存在TLE * 1和RE * 2。
#include <cstdio>

#define MAXN 500005
#define MAXM 500005 int n, m, u[MAXN], v[MAXN], tw[MAXN], ts, p, to, tb[MAXN], th[MAXN];
int w[MAXN], b[MAXN], s, h[MAXN], o, lik[MAXN];
int dfn[MAXN], low[MAXN], ins[MAXN], st[MAXN], tot, t, tim;
int f[MAXN], ans; struct Edge {
int v, next;
} e[MAXM], te[MAXM]; int min(int a, int b) {
return a < b ? a : b;
} int max(int a, int b) {
return a > b ? a : b;
} void add(int u, int v, int t) {
if (t) to++, te[to] = (Edge) {v, th[u]}, th[u] = to;
else o++, e[o] = (Edge) {v, h[u]}, h[u] = o;
} void init() {
scanf("%d %d", &n, &m);
for (int i = ; i <= m; i++) scanf("%d %d", &u[i], &v[i]), add(u[i], v[i], );
for (int i = ; i <= n; i++) scanf("%d", &tw[i]);
scanf("%d %d", &ts, &p);
for (int i = ; i <= p; i++) scanf("%d", &o), tb[o] = ;
} void tarjan(int o) {
dfn[o] = low[o] = ++tim, ins[o] = , st[++t] = o;
for (int x = th[o]; x; x = te[x].next) {
int v = te[x].v;
if (!dfn[v]) tarjan(v), low[o] = min(low[v], low[o]);
else if (ins[v] && dfn[v] < low[o]) low[o] = dfn[v];
}
if (dfn[o] == low[o]) {
int x; tot++;
do x = st[t--], ins[x] = , lik[x] = tot; while (x != o);
}
} void rebuild() {
for (int i = ; i <= m; i++) if (lik[u[i]] != lik[v[i]]) add(lik[u[i]], lik[v[i]], );
for (int i = ; i <= n; i++) {
w[lik[i]] += tw[i];
if (i == ts) s = lik[i];
if (tb[i]) b[lik[i]] = ;
}
f[s] = w[s];
} void DFS(int o) {
if (b[o]) ans = max(ans, f[o]);
for (int x = h[o]; x; x = e[x].next) {
int v = e[x].v;
if (f[o] + w[v] > f[v]) f[v] = f[o] + w[v], DFS(v);
}
} int main() {
init();
for (int i = ; i <= n; i++) if (!dfn[i]) tarjan(i);
rebuild();
DFS(s);
printf("%d", ans);
return ;
}

[知识点]Tarjan算法的更多相关文章

  1. tarjan算法 POJ3177-Redundant Paths

    参考资料传送门 http://blog.csdn.net/lyy289065406/article/details/6762370 http://blog.csdn.net/lyy289065406/ ...

  2. 有向图强连通分量的Tarjan算法

    有向图强连通分量的Tarjan算法 [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G ...

  3. 点/边 双连通分量---Tarjan算法

    运用Tarjan算法,求解图的点/边双连通分量. 1.点双连通分量[块] 割点可以存在多个块中,每个块包含当前节点u,分量以边的形式输出比较有意义. typedef struct{ //栈结点结构 保 ...

  4. 割点和桥---Tarjan算法

    使用Tarjan算法求解图的割点和桥. 1.割点 主要的算法结构就是DFS,一个点是割点,当且仅当以下两种情况:         (1)该节点是根节点,且有两棵以上的子树;         (2)该节 ...

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

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

  6. (转载)LCA问题的Tarjan算法

    转载自:Click Here LCA问题(Lowest Common Ancestors,最近公共祖先问题),是指给定一棵有根树T,给出若干个查询LCA(u, v)(通常查询数量较大),每次求树T中两 ...

  7. 强连通分量的Tarjan算法

    资料参考 Tarjan算法寻找有向图的强连通分量 基于强联通的tarjan算法详解 有向图强连通分量的Tarjan算法 处理SCC(强连通分量问题)的Tarjan算法 强连通分量的三种算法分析 Tar ...

  8. Tarjan 算法&模板

    Tarjan 算法 一.算法简介 Tarjan 算法一种由Robert Tarjan提出的求解有向图强连通分量的算法,它能做到线性时间的复杂度. 我们定义: 如果两个顶点可以相互通达,则称两个顶点强连 ...

  9. 【小白入门向】tarjan算法+codevs1332上白泽慧音 题解报告

    一.[前言]关于tarjan tarjan算法是由Robert Tarjan提出的求解有向图强连通分量的算法. 那么问题来了找蓝翔!(划掉)什么是强连通分量? 我们定义:如果两个顶点互相连通(即存在A ...

随机推荐

  1. MyEclipse2014配置Tomcat开发JavaWeb程序JSP以及Servlet(转载)

    转载地址:http://blog.csdn.net/21aspnet/article/details/21867241 1.安装准备 1).下载安装MyEclipse2014,这已经是最新版本. 2) ...

  2. 几年前做家教写的C教程(之一)

    C语言学习宝典 首先让我们认识什么是C语言. C语言是一种计算机开发语言,是一种非常基础的开发语言.能够用C语言做很多事情.C语言是顺序执行的程序. 程序应该包括数据描述,数据操作. C语言的数据类型 ...

  3. PHP面向对象编程之深入理解方法重载与方法覆盖(多态)

    这篇文章主要介绍了PHP面向对象编程之深入理解方法重载与方法覆盖(多态)的相关资料,需要的朋友可以参考下: 什么是多态? 多态(Polymorphism)按字面的意思就是"多种状态" ...

  4. git中使用.gitignore文件

    在进行协作开发代码管理的过程中,常常会遇到某些临时文件.配置文件.或者生成文件等,这些文件由于不同的开发端会不一样,如果使用git add . 将所有文件纳入git库中,那么会出现频繁的改动和push ...

  5. PHP+Nginx环境搭配

    一.Nginx安装 nginx可以使用各平台的默认包来安装,本文是介绍使用源码编译安装,包括具体的编译参数信息. 正式开始前,编译环境gcc g++ 开发库之类的需要提前装好,这里默认你已经装好. u ...

  6. JavaScript获取当前根目录

    JavaScript获取当前根目录 主要用到Location 对象,包含有关当前 URL 的信息,是 Window 对象的一个部分,可通过 window.location 属性来访问. 方法一 (wi ...

  7. gdb调试小结

    gdb最基本的调试命令. 1以调试程序test.cpp为例: 进入调试环境 gdb test 2.b 12 在文件的第12行设置断点. 删除断点: info b 列出所有的断点信息 (gdb) inf ...

  8. Educational Codeforces Round 3 E. Minimum spanning tree for each edge LCA/(树链剖分+数据结构) + MST

    E. Minimum spanning tree for each edge   Connected undirected weighted graph without self-loops and ...

  9. Android开发之ViewPager+ActionBar+Fragment实现响应式可滑动Tab

     今天我们要实现的这个效果呢,在Android的应用中十分地常见,我们可以看到下面两张图,无论是系统内置的联系人应用,还是AnyView的阅读器应用,我们总能找到这样的影子,当我们滑动屏幕时,Tab可 ...

  10. 深入理解KMP算法之续篇

    前言: 纠结于KMP已经两天了,相较于本人之前博客中提到的几篇博文,本人感觉这篇文章更清楚地说明了KMP算法的来龙去脉. http://www.cnblogs.com/goagent/archive/ ...