强连通分量(Tarjan算法)和缩点

一些定义

给定一张有向图,对于图中任意两个节点 xxx 和 yyy ,存在从 xxx 到 yyy 的路径,也存在从 yyy 到 xxx 的路径,则称该有向图为强连通图

有向图的强连通子图被称为强连通分量SCC

显然,环一定是强连通图。因为如果在有向图中存在 xxx 到 yyy 的路径,且存在 yyy 到 xxx 的路径,那么 x,yx,yx,y 一定在同一个环中。

对于一个有向图,如果从 rootrootroot 可以到达图中所有的点,则称其为“流图”,而 rootrootroot 为流图的源点。

以 rootrootroot 为原点对流图深度优先遍历,每个点只访问一次,在过程中,所有发生递归的边 (x,y)(x, y)(x,y) 构成的一棵树叫做这个流图的搜索树

在深度优先遍历时,对每个访问到的节点分别进行整数 1...n1...n1...n 标记,该标记被称为时间戳,记为 dfn[x]dfn[x]dfn[x] 。

流图中的每条有向边 (x,y)(x, y)(x,y) 必然是以下四种中的一种:

  1. 前向边,指搜索树中 xxx 是 yyy 的祖先节点
  2. 后向边,指搜索树中 yyy 是 xxx 的祖先节点
  3. 树枝边,指搜索树里的边,满足 xxx 是 yyy 的父节点。
  4. 其他边(好像也叫横叉边),指除了上面三种情况的边。且一定满足 dfn[y]<dfn[x]dfn[y]<dfn[x]dfn[y]<dfn[x]

Tarjan算法之强连通分量

Tarjan 算法基于有向图的深度优先遍历,能够在线性时间中求出一张有向图的各个强连通分量。

其核心思想就是考虑两点之间是否存在路径可以实现往返。

我们在后文中,都会结合搜索树(本身就是深度优先遍历的产物)来考虑,这样就可以在深度优先遍历的同时完成我们的目标。

对于各种边的分析:

对于流图,前向边作用不大,因为当前搜索树中一定存在 xxx 到 yyy 的路径。

后向边就很重要了,因为它一定可以和搜索树中 xxx 到 yyy 的路径组成环。

横叉边需要判断一下,如果这条横叉边能到达搜索树上 xxx 的祖先(显然,xxx 的祖先一定可以到达 xxx)。记这个祖先为 zzz,则这条横叉边一定能和它到 zzz 的路径,zzz 到 xxx 的路径组成环。

强连通分量的维护和栈的引入:

为了找到横叉边与后向边组成的环,我们考虑在深度优先遍历的同时维护一个栈。

当遍历到 iii 节点时,栈里一定有以下一些点:

  1. 搜索树上 iii 的所有祖先集合 jjj。若此时存在后向边 (i,j)(i, j)(i,j),则 (i,j)(i, j)(i,j) 一定与 jjj 到 iii 的路径形成环。(后向边组成的环)
  2. 已经访问过的点 kkk,且满足从 kkk 出发一定能找到到 jjj 的路径。此时,iii 到 kkk 的路径,kkk 到 jjj 的路径,jjj 到 iii 的路径一定会形成环。(横叉边和后向边组成的环)

于是我们引入回溯值的概念。回溯值 low[x]low[x]low[x] 表示以下节点的最小时间戳:

  1. 该点在栈中。
  2. 存在一条从流图的搜索树中以 xxx 为根的子树为起点出发的有向边,以该点为终点。(也就是以它为起点能继续往下遍历到的点)

如果当前的 low[x]low[x]low[x] 表示的最小时间戳代表的点集全是2类点,则易得 low[x]=dfn[x]low[x]=dfn[x]low[x]=dfn[x] 时强连通分量存在,且 xxx 是此强连通分量的根(整个强连通分量中时间戳最小的节点)。

如果表示的点集存在1类点。则当前点一定属于强连通分量,且该强连通分量的根为整个强连通分量中时间戳最小的节点。

当我们判断了存在以当前点为根的强连通分量后,从栈中不断取出点,直到取出的点与当前点相等,我们就得到了整个强连通分量的信息。

整理更新回溯值的方法:

如果当前点第一次被访问,入栈,且low[x]=dfn[x]low[x]=dfn[x]low[x]=dfn[x] 。

遍历从xxx为起点的每一条边 (x,y)(x,y)(x,y) 。若 yyy 被访问过,且 yyy 在栈中,那么 low[x]=min(low[x],dfn[y])low[x]=min(low[x],dfn[y])low[x]=min(low[x],dfn[y])。若y没被访问过,先递归访问y,在回溯之后更新 low[x]=min(low[x],low[y])low[x]=min(low[x],low[y])low[x]=min(low[x],low[y]) 。


具体实现:

1 邻接矩阵建图

#include<bits/stdc++.h>
using namespace std;
#define MAXN 200005
#define X 114514
int n;//一共有n个点
//强连通分量相关定义int dfn[MAXN],low[MAXN];//时间戳以及回溯值(最小时间戳)
struct node
{int mx;//集团中点权的最大值int sum;//缩点后的边权和//其他信息根据具体题目具体设置
}sccc[MAXN];//储存最后求出的各个强连通分量的信息
int key[MAXN];//key[i]表示i在编号为key[i]的强连通分量中
int num=0;//时间戳
int scc=0;//强连通分量个数
stack <int> s;
int visit[MAXN];//记录是否在栈中//vector <int> original_mp[MAXN];//原始图void tarjan(int x)
{low[x]=dfn[x]=++num;s.push(x);visit[x]=1;for(auto y:original_mp[x])//遍历原始图{if(dfn[y]==0){tarjan(y);low[x]=min(low[x],low[y]);}else if(visit[y]==1){low[x]=min(low[x],dfn[y]);}}if(dfn[x]==low[x])//维护这个强连通分量{scc++;int now=-1;while(x!=now){now=s.top();s.pop();visit[now]=0;key[now]=scc;sccc[scc].mx=X;sccc[scc].sum=X;//...其他信息根据具体题目具体分析,这里只是举例维护的方法}}
}void init()
{scc=0;num=0;for(int i=1;i<=n;i++){key[i]=i;dfn[i]=0;low[i]=0;visit[i]=0;//sccc[i].clear(); 要不要清除,怎么清除,根据具体题目来}
}void solve()
{init();for(int i=1;i<=n;i++){if(dfn[i]==0)//如果当前点没被遍历过,跑一遍Tarjantarjan(i);}
}

2 邻接表建图

#include<bits/stdc++.h>
using namespace std;
#define MAXN 200005//点数
#define MAXM 500005//边数
#define X 114514
int n,m;//点和边的数量
//邻接表相关定义
struct Edge
{int to,next;
}edge[MAXM];
int head[MAXN],tot;
////强连通分量相关定义
int low[MAXN],dfn[MAXN],key[MAXN],visit[MAXN];
stack <int> s;
int scc=0;//强连通分量个数
int num=0;//时间戳个数
struct node
{int mx;//集团中点权的最大值int sum;//缩点后的边权和//其他信息根据具体题目具体设置
}sccc[MAXN];//储存最后求出的各个强连通分量的信息int siz[MAXN];//各个强连通分量包含点的个数,不一定需要
//void init()
{tot=0;for(int i=0;i<=m;i++) head[i]=-1;scc=0;num=0;for(int i=1;i<=n;i++){key[i]=i;dfn[i]=0;low[i]=0;visit[i]=0;//sccc[i].clear(); 要不要清除,怎么清除根据具体题目来}
}void tarjan(int x)
{low[x]=dfn[x]=++num;//更新时间戳s.push(x);visit[x]=1;//入栈for(int i=head[x];i!=-1;i=edge[i].next)//遍历原始图{int y=edge[i].to;if(dfn[y]==0){tarjan(y);low[x]=min(low[x],low[y]);}else if(visit[y]==1){low[x]=min(low[x],dfn[y]);}}if(low[x]==dfn[x])//维护当前强连通分量{scc++;int now=-1;while(now!=x){now=s.top();s.pop();visit[now]=0;//出栈标记//维护当前强连通分量内节点信息key[now]=scc;sccc[scc].mx=X;sccc[scc].sum=X;}}
}void solve()
{init();for(int i=1;i<=n;i++){if(dfn[i]==0)tarjan(i);}
}

缩点

缩点其实就是指的把环看成一个点来进行后面的图论算法。而把环看成的这个点的点权在题目中会具体说明。

比较常见的缩点后的点权是整个环路中所有点的点权和。

如下图:

1 -> 2 -> 4 -> 5 -> 2 -> 3

显然上图存在环路,在经过缩点后,我们可以将它变成这样:

1 -> 2(val[2]+val[4]+val[5]) -> 3

具体实现:

#include<bits/stdc++.h>
using namespace std;
#define MAXN 200005//点
int n;//点
int key[MAXN];//之前定义的,表示这个点位于哪个强连通分量
vector <int> original_mp[MAXN];//原图
vector <int> mp[MAXN];//缩点后的图void solve()
{for(int i=1;i<=n;i++){for(auto j:original_mp[i]){if(key[i]==key[j]) continue;//如果这条边的两端同属于一个强连通分量,放弃连边mp[key[i]].emplace_back(key[j]);//将两个点对应的强连通分量的编号相连,加入新图。//显然,单个点也属于一个强连通分量}}
}

强连通分量(Tarjan算法)和缩点相关推荐

  1. 强连通分量(Tarjan算法) 图解

    强连通分量(Tarjan算法) 前言 第一件事:没事不要while(m–),会带来不幸 第二件事:看博客先看看评论,如果博主他写错了的话- 简介 先讲几个定义 强连通:两个顶点 uuu,vvv 可以相 ...

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

    转自:http://www.byvoid.com/blog/scc-tarjan/ http://blog.csdn.net/geniusluzh/article/details/6601514 在有 ...

  3. 强连通分量——tarjan算法缩点

    一. 什么是强连通分量? 强连通分量:在有向图G中,如果两个顶点u,v间(u->v)有一条从u到v的有向路径,同时还有一条从v到u的有向路径,则称两个顶点强连通(strongly connect ...

  4. 有向图的强连通分量--Tarjan算法---代码分析

    本文就是做个代码分析,顺便说下理解. 一.预备知识: 需要知道什么是: 回边.前向边.交叉边 二.上代码: #include<algorithm> #define NIL -1using ...

  5. 图之强连通、强连通图、强连通分量 Tarjan算法

    一.解释 在有向图G中,如果两个顶点间至少存在一条互相可达路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强 ...

  6. 算法学习:强连通分量 --tarjan

    [定义] [强连通分量] 在一个子图中,任意点能够直接或者间接到达这个子图中的任意点,这个子图被称为强连通分量 [解决问题] 求图的强连通分量 同时能够起到 ...................缩点 ...

  7. POJ 2186 挑战 --牛红人 强连通分量——Tarjan

    题意:n头奶牛,给出若干个欢迎关系a b,表示a欢迎b,欢迎关系是单向的,但是是可以传递的,如:a欢迎b,b欢迎c,那么a欢迎c .另外每个奶牛都是欢迎他自己的.求出被所有的奶牛欢迎的奶牛的数目.#i ...

  8. 求无向图的连通分量或有向图的强连通分量—tarjan()ccf高速公路

    概念定义: 在图论中,连通图基于连通的概念. 1. 连通(无向图): 若顶点Vi能通过路径到达Vj,那么称为Vi和Vj是连通的 对无向图:若从顶点Vi到顶点Vj有路径相连(当然从j到i也一定有路径), ...

  9. 【HDU - 5934】Bomb (强连通分量Tarjan + 缩点)

    题干: There are NN bombs needing exploding. Each bomb has three attributes: exploding radius riri, pos ...

最新文章

  1. python网络爬虫程序技术,Python网络爬虫程序技术
  2. Group by优化
  3. 如何修改 Chrome 默认的 font-size
  4. http协议相关内容
  5. MySQL使用学习使用,mysql学习--基本使用_MySQL
  6. Unity UGUI——UI基础,Canvas
  7. 笔记本电脑键盘切换_2019年最好的2500元以内的笔记本电脑
  8. html5试卷答案,常见的HTML5前端面试题及答案
  9. windows上安装Metasploit Framework 4.0
  10. 华为手机解锁码计算工具_华为高通全系列手机解锁工具
  11. 如何设计带限流功能的5V供电电路?快来学!
  12. 微信支付的商户号和服务商的区别,以及什么是子账户
  13. 图像原点矩、二阶中心矩物理意义推导
  14. linux装pl2303驱动下载,Linux下安装USB转串口驱动(PL2303)
  15. MCAL中MCU的配置
  16. python将多个列表合并_Python将多个list合并为1个list的方法
  17. 百度智能云开物工业互联网平台解决方案亮相2021服贸会成果发布会
  18. 乔迁之喜!泛微软件园启用,欢迎新老朋友来坐坐
  19. BERT-Whole Word Masked(WWM)
  20. libnet编译linux,Linux 网络编程—— libnet 使用指南

热门文章

  1. ios 中字符串怎么换行
  2. PTA1018 锤子剪刀布
  3. CSDN博客字体样式、字体大小、字体颜色、首行缩进调节
  4. python两两组合_python – 一个列表中的两个组合列表
  5. Comparative Molecular Analysis of Gastrointestinal Adenocarcinomas
  6. 洛丽运动会 NFT 作品集第一弹
  7. Vue Antdv 上传组件(a-upload、a-upload-dragger)二次封装(DZMAntdvUpload)
  8. 计算机英语构词法,计算机专业英语的构词方法
  9. 新1期_012课_内存控制器与SDRAM
  10. Sketch-Based Image Retrieval