一文轻松搞定 tarjan 算法(二)(附带 tarjan 题单)
完结篇:tarjan 求割点、点双连通分量、割边(桥)(附 40 道很好的 tarjan 题目)。
tarjan 求割点
还是求强联通分量的大致思路捏.
算法思路:
我们把图中的点分为两种: 每一个联通子图搜索开始的根节点 和 其他点。
判断是不是割点的方式如下:
对于根节点:
记一下在跑 tarjan 过程中从这个点出发的未被搜索到的子节点的数量 \(child\),如果 \(child\ge 2\),那么这个点为割点,否则就不是割点;- 证明(很简单,建议自己先想一想为什么或者手模个图,实在不懂再看证明):
设根节点为点 \(u\),如果 \(child\) 为 1 的话,说明根节点的不同子树上的点可以不经过点 \(u\) 而互相到达,也就是说即使删了 \(u\) 点,图中所有点照样可以互相到达,则 \(u\) 不为割点。反之,同样也易证得为割点。
对于其他点:
判断一个点 \(u\) 有没有一个子节点 \(v\) 使得low[v] >= dfn[u]
,若存在,则 \(u\) 为割点,否则不为割点。- 证明:
因为我们在无向图中跑 tarjan 时,已经特判父节点或边的编号来避免走“回头路”了(详见上文求边双部分),所以如果满足判断条件时,说明 \(v\) 通过返祖边只能到达 \(u\) 及其子树部分,并不能到达 \(u\) 的祖先节点,所以若 \(u\) 点删去,那么 \(v\) 点便与 \(u\) 以上部分断开了,此时显然 \(u\) 为割点。
会判断这两类点是不是割点之后就做完了,相信大家也知道该怎么求了。看代码吧!
算法代码:
相比于求边双部分增加了数组 cut[x]
来判断 \(x\) 是不是割点,若为 true 则 \(x\) 是割点,否则不是。
void tarjan(int x, int p){
low[x] = dfn[x] = ++th;
s[++top] = x; int child = 0;
for(int i=head[x]; i; i=nxt[i]){
int y = to[i];
if(y == p) continue;
if(!dfn[y]){
tarjan(y, x);
low[x] = min(low[x], low[y]);
if(low[y] >= dfn[x]){
child++;
if(p != 0 or child > 1) cut[x] = 1;
}
}
else low[x] = min(low[x], dfn[y]);
}
}
为什么 p == 0
说明 \(x\) 为根节点呢,大家肯定知道啦!
因为主函数中是这么写的:
for(int i=1; i<=n; i++)
if(!dfn[i]) tarjan(i, 0);
tarjan 求点双连通分量
我该先写求点双好呢还是先写求割边好呢,这俩都是需要割点的相关知识的,啊选择困难症(
但是的但是,学会求割点之后那求点双(简称 BCC)就很简单啦 啦啦小魔仙,玛卡巴卡,卡巴露露,摇身变!
算法思路:
性质:无向连通图中割点一定属于至少两个BCC,非割点只属于一个BCC
如此图:2 号点是个割点,其他点则不是。有红、蓝两个 BCC。
有一条显然的结论:每个点双,它在 dfs 时最先被发现的点一定是割点或者 dfs 树的树根。
证明:这很显然吧?!根据割点的定义自己理解一下,不证明。算了,还是简单说一下吧:我们知道割点是 BCC 的交点,即 BCC 通过割点连接,从一个 BCC 到另一个 BCC 一定是从经过割点开始的,所以证得。
那么这条结论其实就等价于 每个 BCC 都在其最先被发现的点(一个割点或根节点)的子树中。那么我们在上文求割点方法基础上每找到一个割点(或根节点)后,其子树(包含自己)便是一个点双连通分量了。
实现:
我们还是维护一个栈,存点,每当搜索到一个点时就将该点入栈,找到割点(就是找到一个 BCC)时将栈顶到该割点所有元素依次出栈,(但注意:割点并不出栈,因为上文已说一个割点属于两个 BCC,它还需要来更新另一个 BCC,所以先不出栈,特判就行。)那么出栈的元素以及割点就是所求的点双了。
算法演示:
如上图中,我们以 1 为根开始搜索;
搜索到 2 节点时,继续递归 2 -> 3 -> 4;发现 \(low_4 = 2 < dfn_3\),那么 3 号点则不是割点,回溯;
而 \(low_3 = dfn_2\),所以 \(2\) 号点是割点,那么将此时栈中从栈顶到 \(2\) 号点所有元素出栈形成点双;
此时栈从栈尾到栈顶依次是:1,2,3,4。那么便是 2,3,4 构成一个点双(但 2 还在栈中)。
继续回溯到 2 -> 1;发现 1 号点是根节点,也将栈中元素出栈(这时 1 是根节点,所以 1 也出栈),那么 1,2 就又构成了一个点双。
算法代码:
void tarjan(int x, int p){
low[x] = dfn[x] = ++th;
s[++top] = x;
if(!p and !head[x]){ // 特判孤点
BCC[++bcc].emplace_back(x);
top--; return;
}
for(int i=head[x]; i; i=nxt[i]){
int y = to[i];
if(y == p) continue;
if(!dfn[y]){
tarjan(y, x);
low[x] = min(low[x], low[y]);
if(low[y] >= dfn[x] or !p){ //是割点或者根节点
++bcc;
do BCC[bcc].emplace_back(s[top]);
while(s[top--] != y);
BCC[bcc].emplace_back(x);
}
}
else low[x] = min(low[x], dfn[y]);
}
}
例题:
【模板】点双连通分量
板子题练练手。注意这题需要判孤点情况。
tarjan 求割边(桥)
太简单啦!
和割点差不多,改一条:low[y] > dfn[x]
,并且不需要特判根节点了(因为 边 != 点)。
解释:(在判边保证不走回头路的条件下)\(low_y = dfn_x\) 时,说明不通过从 \(x -> y\) 这条路径, \(y\) 也照样可以回到 \(x\) 节点,那么就保证从 \(y\) 到 \(x\) 有两条路径可走了,所以 \(x->y\) 这条路不是割边。
那么完了!
算法代码:
cut[i] = true;
表示 \(i\) 这条路为割边。
void tarjan(int x, int p){
low[x] = dfn[x] = ++th;
s[++top] = x;
for(int i=head[x]; i; i=nxt[i]){
int y = to[i];
if(y == p) continue;
if(!dfn[y]){
tarjan(y, x);
low[x] = min(low[x], low[y]);
if(low[y] > dfn[x]){
cut[i] = true;
}
}
else low[x] = min(low[x], dfn[y]);
}
}
题目练习:
这有个很好的tarjan 题单,从模板到进阶,题都很好,推荐给大家。
所含题目如下
P1656 炸铁路
P1455 搭配购买
P3916 图的遍历
P2835 刻录光盘
P1073 [NOIP2009 提高组] 最优贸易
P2863 [USACO06JAN]The Cow Prom S
P8436 【模板】边双连通分量
P8287 「DAOI R1」Flame
P2002 消息扩散
P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G
P3387 【模板】缩点
P3388 【模板】割点(割顶)
P8435 【模板】点双连通分量
P1407 [国家集训队]稳定婚姻
P2194 HXY烧情侣
P2746 [USACO5.3]校园网Network of Schools
P2812 校园网络【[USACO]Network of Schools加强版】
P2941 [USACO09FEB]Surround the Islands S
P2860 [USACO06JAN]Redundant Paths G
P3398 仓鼠找 sugar
P2169 正则表达式
P3627 [APIO2009] 抢掠计划
P2656 采蘑菇
P4306 [JSOI2010]连通数
P5676 [GZOI2017]小z玩游戏
P1656 炸铁路
P1455 搭配购买
P3916 图的遍历
P2835 刻录光盘
P1073 [NOIP2009 提高组] 最优贸易
P2863 [USACO06JAN]The Cow Prom S
P8436 【模板】边双连通分量
P8287 「DAOI R1」Flame
P2002 消息扩散
P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G
P3387 【模板】缩点
P3388 【模板】割点(割顶)
P8435 【模板】点双连通分量
P1407 [国家集训队]稳定婚姻
P2194 HXY烧情侣
P2746 [USACO5.3]校园网Network of Schools
P2812 校园网络【[USACO]Network of Schools加强版】
P2941 [USACO09FEB]Surround the Islands S
P2860 [USACO06JAN]Redundant Paths G
P3398 仓鼠找 sugar
P1262 间谍网络
P4742 [Wind Festival]Running In The Sky
P8867 [NOIP2022] 建造军营
P3469 [POI2008]BLO-Blockade
P2515 [HAOI2010]软件安装
P5058 [ZJOI2004]嗅探器
P7687 [CEOI2005] Critical Network Lines
P7924 「EVOI-RD2」旅行家
P5236 【模板】静态仙人掌
P3225 [HNOI2012]矿场搭建
P4716 【模板】最小树形图
P4126 [AHOI2009]最小割
P6335 [COCI2007-2008#1] STAZA
P4637 [SHOI2011]扫雷机器人
P5236 【模板】静态仙人掌
P4716 【模板】最小树形图
P4436 [HNOI/AHOI2018]游戏
End
历时多天,终于把这两篇 tarjan 写完了。
tarjan 都学了,那下一章包得是圆方树的啦(
\(敬请期待......\)
一文轻松搞定 tarjan 算法(二)(附带 tarjan 题单)的更多相关文章
- 轻松搞定 easyui datagrid 二次加载的问题(转)
对于使用url方式的初学者,经常碰到重复请求的问题,这个问题的根源是因为一旦设置了url参数,Datagrid组件在实例化的时候就会做请求,如何避免二次加载这样问题呢,个人觉得注意以下两点基本就可以防 ...
- 轻松搞定RabbitMQ(二)——工作队列之消息分发机制
转自 http://blog.csdn.net/xiaoxian8023/article/details/48681987 上一篇博文中简单介绍了一下RabbitMQ的基础知识,并写了一个经典语言入门 ...
- 【微服务】之二:从零开始,轻松搞定SpringCloud微服务系列--注册中心(一)
微服务体系,有效解决项目庞大.互相依赖的问题.目前SpringCloud体系有强大的一整套针对微服务的解决方案.本文中,重点对微服务体系中的服务发现注册中心进行详细说明.本篇中的注册中心,采用Netf ...
- 【微服务】之三:从零开始,轻松搞定SpringCloud微服务-配置中心
在整个微服务体系中,除了注册中心具有非常重要的意义之外,还有一个注册中心.注册中心作为管理在整个项目群的配置文件及动态参数的重要载体服务.Spring Cloud体系的子项目中,Spring Clou ...
- 【数据结构】 最小生成树(四)——利用kruskal算法搞定例题×3+变形+一道大水题
在这一专辑(最小生成树)中的上一期讲到了prim算法,但是prim算法比较难懂,为了避免看不懂,就先用kruskal算法写题吧,下面将会将三道例题,加一道变形,以及一道大水题,水到不用高级数据结构,建 ...
- 盘它!基于CANN的辅助驾驶AI实战案例,轻松搞定车辆检测和车距计算!
摘要:基于昇腾AI异构计算架构CANN(Compute Architecture for Neural Networks)的简易版辅助驾驶AI应用,具备车辆检测.车距计算等基本功能,作为辅助驾驶入门级 ...
- Webcast / 技术小视频制作方法——自己动手录制video轻松搞定
Webcast / 技术小视频制作方法——自己动手录制video轻松搞定 http://blog.sina.com.cn/s/blog_67d387490100wdnh.html 最近申请加入MSP的 ...
- 从零开始,轻松搞定SpringCloud微服务系列
本系列博文目录 [微服务]之一:从零开始,轻松搞定SpringCloud微服务系列–开山篇(spring boot 小demo) [微服务]之二:从零开始,轻松搞定SpringCloud微服务系列–注 ...
- 【微服务】之四:轻松搞定SpringCloud微服务-负载均衡Ribbon
对于任何一个高可用高负载的系统来说,负载均衡是一个必不可少的名称.在大型分布式计算体系中,某个服务在单例的情况下,很难应对各种突发情况.因此,负载均衡是为了让系统在性能出现瓶颈或者其中一些出现状态下可 ...
- PDF怎么旋转页面,只需几步轻松搞定!
有时候我们下载一个PDF文件里面有页面是旋转的情况,用手机看的时候可以把手机旋转过来看,那么用电脑的时候总不可能也转过来看吧,笔记本是可以的台式的是不行的,这个时候我们就需要把PDF文件中旋转的页面转 ...
随机推荐
- c++17 using继承所有构造函数
//使用using继承所有的构造函数 #include "tmp.h" #include <iostream> using namespace std; struct ...
- 浅谈Git架构和如何避免代码覆盖的事故
浅谈Git架构和如何避免代码覆盖的事故 Git 不同于 SVN 的地方在于, Git 是分布式的版本管理系统, 所有的客户端和服务器都保存了一份代码, 涉及到仓库仓之间的同步, 所以处理不当极易造成冲 ...
- AI/机器学习(计算机视觉/NLP)方向面试复习1
1. 判断满二叉树 所有节点的度要么为0,要么为2,且所有的叶子节点都在最后一层. #include <iostream> using namespace std; class TreeN ...
- Fiddler使用界面介绍-左侧会话面板
左侧会话面板,是Fiddler抓取的请求数据展示
- Jmeter参数化1-随机数设置
背景:当新增接口的某个字段是唯一性,每次调用该新增接口都会需要单独传入这个字段,麻烦且繁琐. 解决:jmeter设置随机数参数,然后接口调用该参数就达到了自动性不再需要人工传入不同的值.方便调用接口, ...
- Jmeter函数助手11-BeanShell
BeanShell函数用于简单的计算或者运行编程脚本. 表达式求值:填入脚本代码或脚本文件${__BeanShell(source("test.bsh"))} 存储结果的变量名(可 ...
- 【JS】05 DOM 文档对象模型 P2 元素的CRUD、Dom集合对象
Element & Node 元素,或者称为节点 在JS中创建一个HTML元素,但是因为没有指定在Dom对象中的节点位置,所以页面不会发生改变 var para = document.crea ...
- Jupyter Lab和Jupyter Notebook的区别
JupyterLab与Jupyter Notebook:详细比较 简介 Jupyter Notebook是一个开源的Web应用程序,允许用户创建和共享包含实时代码.方程.可视化和解释性文本的文档.Ju ...
- 2024-08-03:用go语言,给定一个从 0 开始的字符串数组 `words`, 我们定义一个名为 `isPrefixAndSuffix` 的布尔函数,该函数接受两个字符串参数 `str1` 和
2024-08-03:用go语言,给定一个从 0 开始的字符串数组 words, 我们定义一个名为 isPrefixAndSuffix 的布尔函数,该函数接受两个字符串参数 str1 和 str2. ...
- 数字人 —— 虚拟人 —— Inworld AI用生成式AI——生成式游戏NPC
相关: https://www.ithome.com/0/756/603.htm https://baijiahao.baidu.com/s?id=1774732295233220838 https: ...