// 此博文为迁移而来,写于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. cutpFTP设置步骤

    cutpFTP设置步骤 平常时为了方便两台电脑之间传送数据,我们可以使用cutpftp这个工具实现,而且cutpftp还具有定时传送的功能,非常方便使用.以下是使用该工具的“同步文件夹”功能同步两台电 ...

  2. Android中Service 使用详解(LocalService + RemoteService)

    Service 简介: Service分为本地服务(LocalService)和远程服务(RemoteService): 1.本地服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外L ...

  3. “init terminating in do_boot” Windows10 Rabbit MQ fails to start

    在Windows 10环境下安装rabbitmq-server-3.6.2后,CMD中运行命令:rabbitmq-plugins enable rabbitmq_management 报错: { , ...

  4. 说说JSON和JSONP,也许你会豁然开朗,含jQuery用例 分类: JavaScript 2014-09-23 10:41 218人阅读 评论(1) 收藏

    前言: 由于Sencha Touch 2这种开发模式的特性,基本决定了它原生的数据交互行为几乎只能通过AJAX来实现. 当然了,通过调用强大的PhoneGap插件然后打包,你可以实现100%的Sock ...

  5. jquery获取、改变元素属性值

    //标签的属性称作元素属性,在JS里对应的DOM对象的对应属性叫DOM属性.JS里的DOM属性名有时和原元素属性名不同. //==================================操作元 ...

  6. 向Word模板中填充数据

    现在有这样的需求,给Word文档的指定位置填充上特定数据,例如我们有一个终端,用来打印员工的薪资证明,对于一个公司来说,他的薪资证明模板是固定的,变化的地方是员工姓名,部门,职位等.我们只需要将这些指 ...

  7. Ubuntu下快速安装php环境

    今天蛋疼了一下,在Ubuntu下装了一下php的环境,也就是装了一下MySQL.PHP.Apache.话说还真是简单...不禁让我想起原来在windows下开发的时候撑死就是装不上,而且一个就是几个G ...

  8. JDK 1.5 1.6 override区别

    今天在更新时发现有个别项目报错,报错信息 到网上搜索了之后,根据网上描述,修改了一批配置都不行: http://bestchenwu.iteye.com/blog/997420(这个里面的方法二,即为 ...

  9. PMP 第二章 项目生命周期与组织

    1 项目组织机构类型有哪些? 区别是什么? 职能型  矩阵型  项目性 2 什么是事业环境因素? 什么是组织过程资产? 如何区分事业环境因素和组织过程资产? 事业环境因素:事业环境因素指围绕项目或能影 ...

  10. BurpSuite抓App数据包的方法

    软件准备: 1.猎豹wifi 2.BurpSuite或者fillder都可以 查看电脑IP地址: 网卡ip: 确保无线网卡的IP和手机的代理IP保持一致即可