算法描述

tarjan算法思想:从一个点开始,进行深度优先遍历,同时记录到达该点的时间(dfn记录到达i点的时间),和该点能直接或间接到达的点中的最早的时间(low[i]记录这个值,其中low的初始值等于dfn)。如图:

  假设我们从1开始DFS,那么到达1的时间为1,到达2的时间为2,到达3的时间为3。同时,点1能直接或间接到达的点中,最小时间为1,点2能通过3间接到达点1,所以点2可到达最早的点时间为1,点3可以直接到达点1,故点3到达的最早的点的时间为1。)。对于每一个没有被遍历到的点A,如果从当前点有一条到未遍历点A的有向边,则遍历到A,同时将点A入栈,时间戳+1并用dfn[a]记录到达点A的时间,枚举从A发出的每一条边,如果该边指向的点没有被访问过,那么继续dfs,回溯后low[a]=min(low[a],low[j])(其中j为A可以到达的点。)如果该点已经访问过并且该点仍在栈里,那么low[a]=min(low[a],dfn[j])。

解释:

  若点j没有被访问过,那么回溯后low[j]就是j能到达最早点,a能到达的最早点当然就是a本身能到达的最早点,或者a通过j间接到达的最早点。若点j已经被访问过,那么low[j]必然没有被回溯所更新。所以low[a]就等于a目前能到达的最小点或a直接到达点j时点j的访问时间。注意:两个句子中的“或”其实指的是两者的最小值。

那么如果我们回溯到一个点K他的low[k]=dfn[k]那么我们将K及其以前在栈中的点依次弹出,这些点即为一个强连通分量。(说明从k出发又回到k)

证明:

  因为该点dfn=low,所以在栈中的该点以上的点都能由该点直接或间接的到达。同时栈中在该点前的任意一点j,其dfn[j] != low[j](否则点j比点k靠前,又因为dfn[j]=low[j],j一定先被弹出了。)那么这个点j通过low[j]这个时间的点,一定能到达点k,否则,low[j]能到达点i,又因为dfn>=low所以有2种情况1、dfn>low:那么我们可以找到前面一个更小的点。2、dfn=low:应该在回溯到i的时候就找到了一个强连通分量,从而出栈了。而点k前的点没有出栈,证明其中任意一点都能直接或者间接到达点k,进而证明这些点可以两两互达。

先用简单模板刷一道水题入门:

迷宫城堡

本题的边不带权值,可以用vector表示邻接链表:

#include <cstdio>
#include <memory.h>
#include <vector>
#include <algorithm> using namespace std; //本题的顶点号从1到n,故可直接用作vector的下标,不需要用head数组离散化 const int MAXN = ; int top;
int Stack[MAXN];
bool inStack[MAXN];
//DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号
int dfn[MAXN], low[MAXN];
int Bcnt, Dindex; //记录强连通的个数和当前时间
vector<int> ve[MAXN]; //邻接表保存边 void init() {
Bcnt = Dindex = top = ;
memset(dfn, -, sizeof dfn);
memset(inStack, false, sizeof inStack);
for (int i = ; i < MAXN; ++i) ve[i].clear();
} void tarjan(int u) {
int v = ;
dfn[u] = low[u] = ++Dindex;
inStack[u] = true;
Stack[++top] = u;
int t = ve[u].size();
for (int i = ; i < t; ++i) {
v = ve[u][i];
if (dfn[v] == -) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (inStack[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
Bcnt++;
do {
v = Stack[top--];
inStack[v] = false;
} while (u != v);
}
} int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int n, m;
while (scanf("%d%d", &n, &m) != EOF) {
if (m == && n == ) break;
init();
while (m--) {
int a, b;
scanf("%d%d", &a, &b);
ve[a].push_back(b);
}
for (int i = ; i <= n; ++i)
if (dfn[i] == -) tarjan(i);
if (Bcnt == ) puts("Yes");
else puts("No");
} return ;
}

Summer Holiday

只需找出所有的强连通分量,然后遍历所有的边,对于边u->v,若u、v在不同的连通子图,显然v的子图可以从边u->v达到,对于这样的两个强连通分量,可以视为一个子图,只需打电话给u所所在的子图中人,然后让这个人再联系u,v所在的两个强连通分量中的所有人。故只需找出所有的子图,打给每个子图中话费最低的那个人即可。

#include <cstdio>
#include <memory.h>
#include <algorithm> const int MAXN = ;
struct N {
int u, v;//u to v
int next;//下一个顶点
} edge[MAXN * ]; //m <= 2000
int head[MAXN], edgenum; int DFN[MAXN], Low[MAXN], Dindex;//到达某顶点的费用,该子图最小费用 ,当前费用
int Bcnt, Belong[MAXN]; //强连通分量的个数, 当前顶点所属的连通分量
int Stack[MAXN], top;
bool inStack[MAXN]; void addedge(int u, int v) {
N e = {u, v, head[u]};
edge[edgenum] = e;
head[u] = edgenum++;
} void init() {
edgenum = top = Bcnt = Dindex = ;
memset(head, -, sizeof head);
memset(DFN, -, sizeof DFN);
memset(inStack, false, sizeof inStack);
} void tanjar(int u) {
int v = ;
DFN[u] = Low[u] = ++Dindex;
Stack[++top] = u;
inStack[u] = true;
for (int i = head[u]; i != -; i = edge[i].next) {
v = edge[i].v;
if (DFN[v] == -) {
tanjar(v);
Low[u] = std::min(Low[u], Low[v]);
} else if (inStack[v])
Low[u] = std::min(Low[u], DFN[v]);
}
if (DFN[u] == Low[u]) {
++Bcnt;
do {
v = Stack[top--];
Belong[v] = Bcnt; //将点加入该强连通图
//printf("bcnt = %d v = %d\n", Bcnt, v);
inStack[v] = false;
} while (u != v);
}
} int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int n, m;
int w[MAXN] = {};
while (scanf("%d%d", &n, &m) != EOF) {
init();
for (int i = ; i <= n; ++i)
scanf("%d", &w[i]);
while (m--) {
int a, b;
scanf("%d%d", &a, &b);
addedge(a, b);
}
for (int i = ; i <= n; ++i) {
if (DFN[i] == -) tanjar(i);
}
//合并强连通图
int mfee[MAXN] = {};//记录下每个强连图集的最小话费
bool inode[MAXN] = {false}; //不可达的连通图
int pos = ;
for (int i = ; i < edgenum; ++i) {
int u = edge[i].u;
int v = edge[i].v;
if (Belong[u] != Belong[v]) //v所在的连通子图可以由u到达
inode[Belong[v]] = true;
}
for (int i = ; i <= Bcnt; ++i) {
if (!inode[i]) pos++;
mfee[i] = 1e9;
}
for (int i = ; i <= n; ++i) {
int t = Belong[i];
if (!inode[t])
mfee[t] = std::min(mfee[t], w[i]);
}
int sum = ;
for (int i = ; i <= Bcnt; ++i) {
if (mfee[i] != 1e9) sum += mfee[i];
}
printf("%d %d\n", pos, sum);
} return ;
}

Instantaneous Transference

题意:让矿车采到尽可能多的矿。#表示墙,数字表示矿的数目,*表示定点传送,可选择传或者不传,矿车只能向右和向下开,不能倒退。

先来看下SPFA算法,可以求带环最短(最长路径):Currency Exchange

宝藏

#include <cstdio>
#include <memory.h>
#include <vector> using namespace std; const int MAXN = ; inline int Min(int a, int b) {
return a < b ? a : b;
} inline int Max(int a, int b) {
return a > b ? a : b;
} struct arc {
int u, v, next;
} edge[];
int head[MAXN];
int edgenum; int DFN[MAXN], Low[MAXN], Dindex;
int Stack[MAXN], top;
bool inStack[MAXN];
int Belong[MAXN], Bnum[MAXN], Bcnt; void addedge(int u, int v) {
arc na = {u, v, head[u]};
edge[edgenum] = na;
head[u] = edgenum++;
} void init() {
Bcnt = top = Dindex = edgenum = ;
memset(head, -, sizeof head);
memset(DFN, , sizeof DFN);
memset(inStack, false, sizeof inStack);
memset(Bnum, , sizeof Bnum);
} void tarjan(int u) {
int v = ;
DFN[u] = Low[u] = ++Dindex;
Stack[++top] = u;
inStack[u] = true;
for (int i = head[u]; i != -; i = edge[i].next) {
v = edge[i].v;
if (DFN[v] == ) {
tarjan(v);
Low[u] = Min(Low[u], Low[v]);
} else if (inStack[v])
Low[u] = Min(Low[u], DFN[v]);
}
if (DFN[u] == Low[u]) {
Bcnt++;
do {
v = Stack[top--];
inStack[v] = false;
Belong[v] = Bcnt;
Bnum[Bcnt]++;
} while (u != v);
}
} vector<int> G[MAXN];
bool vis[MAXN];
int dfs(int s) {
int maxnum = ;
vis[s] = true;
int size = G[s].size();
for (int i = ; i < size; ++i) {
int v = G[s][i];
if (!vis[v]) {
int tmp = dfs(v);
maxnum = Max(maxnum, tmp);
vis[v] = false;
}
}
return Bnum[s] + maxnum;
} int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int n, m, s;
int caseid = ;
while (scanf("%d%d%d", &n, &m, &s) != EOF) {
init();
while (m--) {
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
}
tarjan(s);
for (int i = ; i <= Bcnt; ++i) G[i].clear();
for (int i = ; i < edgenum; ++i) {
if (DFN[edge[i].u] == ) continue;
int u = Belong[edge[i].u];
int v = Belong[edge[i].v];
if (u != v) G[u].push_back(v);
}
memset(vis, false, sizeof vis);
printf("Case %d:\n%d\n", ++caseid, dfs(Belong[s]));
}
return ;
}

Tarjan算法求有向图的强连通分量的更多相关文章

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

    百度百科 https://baike.baidu.com/item/tarjan%E7%AE%97%E6%B3%95/10687825?fr=aladdin 参考博文 http://blog.csdn ...

  2. Tarjan算法初探 (1):Tarjan如何求有向图的强连通分量

    在此大概讲一下初学Tarjan算法的领悟( QwQ) Tarjan算法 是图论的非常经典的算法 可以用来寻找有向图中的强连通分量 与此同时也可以通过寻找图中的强连通分量来进行缩点 首先给出强连通分量的 ...

  3. Tarjan算法求有向图强连通分量并缩点

    // Tarjan算法求有向图强连通分量并缩点 #include<iostream> #include<cstdio> #include<cstring> #inc ...

  4. 图论-求有向图的强连通分量(Kosaraju算法)

    求有向图的强连通分量     Kosaraju算法可以求出有向图中的强连通分量个数,并且对分属于不同强连通分量的点进行标记. (1) 第一次对图G进行DFS遍历,并在遍历过程中,记录每一个点的退出顺序 ...

  5. (转)求有向图的强连通分量个数(kosaraju算法)

    有向图的连通分量的求解思路 kosaraju算法 逛了很多博客,感觉都很难懂,终于找到一篇能看懂的,摘要记录一下 原博客https://www.cnblogs.com/nullzx/p/6437926 ...

  6. 【数据结构】DFS求有向图的强连通分量

    用十字链表结构写的,根据数据结构书上的描述和自己的理解实现.但理解的不透彻,所以不知道有没有错误.但实验了几个都ok. #include <iostream> #include <v ...

  7. 求有向图的强连通分量个数 之 Kosaraju算法

    代码: #include<cstdio> #include<cstring> #include<iostream> using namespace std; ][] ...

  8. HDU 1269 迷宫城堡 tarjan算法求强连通分量

    基础模板题,应用tarjan算法求有向图的强连通分量,tarjan在此处的实现方法为:使用栈储存已经访问过的点,当访问的点离开dfs的时候,判断这个点的low值是否等于它的出生日期dfn值,如果相等, ...

  9. tarjan算法-解决有向图中求强连通分量的利器

    小引 看到这个名词-tarjan,大家首先想到的肯定是又是一个以外国人名字命名的算法.说实话真的是很佩服那些算法大牛们,佩服得简直是五体投地啊.今天就遇到一道与求解有向图中强连通分量的问题,我的思路就 ...

随机推荐

  1. EF Core » 关系

    对初学者理解关系很有用,先留下来,有时间边看边翻译. Caution 注意 This documentation is for EF Core. For EF6.x and earlier relea ...

  2. fushioncharts的使用教程

    FusionCharts 是使用javascript 实现统计图表的js组件:其官网地址:http://www.fusioncharts.com.其早期版本FusionCharts Free 是基于f ...

  3. C/C++, Java和C#的编译过程解析

    原文地址:http://www.cnblogs.com/rush/p/3155665.html 1.1.1 摘要 我们知道计算机不能直接理解高级语言,它只能理解机器语言,所以我们必须要把高级语言翻译成 ...

  4. Unique Binary Search Trees [LeetCode]

    Given n, how many structurally unique BST's (binary search trees) that store values 1...n? For examp ...

  5. spring关于urlpattern

    视图解析器(ViewResolver)注册中央调度器定制处理器jsp页面搭建springmvc.xml配置效果图第一个案例提升----视图解析器关于urlpattern说法最好配成*.do 不能配成/ ...

  6. js——常见的小方法

    1.随机得到是六位数,可以当做“密码”来使用: Math.random().toString().substr(2, 6):

  7. 开源项目导入eclipse的一般步骤[转]

      下载到开源项目后,我们还是希望导入到eclipse中还看,这样要方便点,一般的步骤是这样的 打开源代码目录, 如果看到里面有.calsspath .project文件,那么说明这个项目本来就是ec ...

  8. word 转 PDF时报错

    利用微软自带的com组件,把word转化成Pdf,利用vs2012调试时没有问题,但是发布到IIS时出错,错误为: 检索 COM 类工厂中 CLSID 为 {} 的组件时失败,原因是出现以下错误: 8 ...

  9. 时钟 IoTimer

    /* 例程是在运行在DISPATCH_LEVEL的IRQL级别 例程中不能使用分页内存 另外在函数首部使用 #pragma LOCKEDCODE */ #include "Driver.h& ...

  10. [Js]布局转换

    为什么要布局转换? 要这样的效果,单写css,只要给每个li浮动就行,不需要绝对定位.但是比如做一些效果(如鼠标移入图片变大),就需要改变位置了.直接给每个li在css上定好位置不方便,也不知道有几个 ...