Tarjan求割点

摘要:
判断一个节点有没有访问过,只需要看其dfn值是否为0,为0则为访问过,不为0则访问过另外模板中无向图不一定连通,所以要将所有的节点都扫描一遍,从未访问过结点的开始DFS即可代码如下:1#include2#include3usingnamespacestd;4intdfn[1000001];//保存时间戳5intdfs_clock;//当前时间6intans[1000001],num;//保存答案7structedge8{9intto,pre;10}edges[2000001];11inthead[1000001],tot;//邻接矩阵存图12inttarjan//tarjan求割点13{14intlowu=dfn[x]=++dfs_clock;//初始化low和dfn15intchild=0,pd=0;//孩子数,是否有子树的low值不小于当前结点的dfn值16for//扫描所有的边17if(!dfn[edges[i].to])//目标结点未访问过(树边)18{19++child;20intlowv=tarjan;//目标结点的low值21if//目标结点low不小于当前结点dfn,当前结点是割点22pd=1;23lowu=min;//更新lowu24}25elseif(edges[i].to!

概述

在一个无向图中,若删除某个点u后连通分量数目增加,则称点u为该无向图的一个割点(cut vertex)

引理

无向连通图DFS树

从一个节点出发进行DFS,将后访问的结点设为前访问结点的孩子,DFS经过的边叫做DFS树的树边(tree edge),第一次处理时从后代(descendant)指向祖先(ancestor)的边叫做返祖边(back edge)

无向连通图的DFS树只存在树边和返祖边,不存在前向边(forward edge)和横叉边(cress edge)(在有向弱连通图中会讲到,前向边是结点指向其非孩子后代的边,横叉边是从一个子树指向另一个子树的边)

证:1、不存在回边:无向图的边无方向,故不存在回边(无重边时)

2、不存在横叉边:假设有横叉边(u,v),假设u比v先访问,则DFS是一定会通过u访问到v,进而访问到它的祖先及子树(貌似不是很严谨?)

定义

dfn[u]表示进入结点u的时间(相对时间戳)

low[u]表示点u及其后代通过返祖边可以访问到的dfn值最小(高度最高)的结点的dfn值

算法

  1. 从起点开始DFS
  2. 访问一个节点,初始化其dfn值和low值均为当前时间戳(每个结点初始化可以到达dfn指最小(高度最高)的结点是自己)
  3. 枚举与当前结点相连的目标结点
    1. 若目标结点已访问过(是当前结点的祖先),更新当前结点的low值(low[u]=min(low[u],dfn[v]))
    2. 若目标结点未访问过(是当前结点的孩子),则对目标结点DFS,并在回溯后用目标结点的low值更新当前结点的low值(low[u]=min(low[u],low[v]))
  4. 判断当前结点是否为割点
    1. 若当前结点为根节点,当且仅当当前结点有不少于两个子树时当前结点为割点
    2. 若当前结点为非根节点,当且仅当当前结点有不少于一个孩子的low值不小于当前结点的dfn值

理解

若当前结点为根节点,当且仅当当前结点有不少于两个子树时当前结点为割点

因为无向连通图的DFS树无横叉边,所以显然当根节点有不少于两个子树时当前结点为割点

Tarjan求割点第1张

若当前结点为非根节点,当且仅当当前结点有不少于一个孩子的low值不小于当前结点的dfn值

若孩子的low值小于当前结点的dfn值,说明上(当前结点的祖先)下(当前结点的后代)在树边之外有边相连,删去当前结点仍连通(不增加连通分量的数量)。因为每个子树均独立,所以当且仅当当前结点至少有一个孩子的low值≥当前结点的dfn值时,当前结点为割点

Tarjan求割点第2张

此处用图象记忆可以加强理解且不易写错

模板

洛谷P3388

写代码的时候有个2小tips:

  1. 对于每个结点,我们只需要当前结点的low值和其子树中结点的low值,且low值是不断向上(由孩子到父亲)更新的,所以我们不需要将low值存放在数组中,只需要写成DFS返回值即可。
  2. 判断一个节点有没有访问过,只需要看其dfn值是否为0,为0则为访问过,不为0则访问过(置过时间戳)

另外模板中无向图不一定连通,所以要将所有的节点都扫描一遍,从未访问过结点的开始DFS即可

代码如下:

Tarjan求割点第3张Tarjan求割点第4张
1 #include<cstdio>
2 #include<algorithm>
3 using namespacestd;
4 int dfn[1000001];//保存时间戳 
5 int dfs_clock;//当前时间 
6 int ans[1000001],num;//保存答案 
7 structedge
8 {
9     intto,pre;
10 }edges[2000001];
11 int head[1000001],tot;//邻接矩阵存图 
12 int tarjan(int x,int fa)//tarjan求割点 
13 {
14     int lowu=dfn[x]=++dfs_clock;//初始化low和dfn 
15     int child=0,pd=0;//孩子数,是否有子树的low值不小于当前结点的dfn值 
16     for(int i=head[x];i;i=edges[i].pre)//扫描所有的边 
17         if(!dfn[edges[i].to])//目标结点未访问过(树边) 
18 {
19             ++child;
20             int lowv=tarjan(edges[i].to,x);//目标结点的low值 
21             if(lowv>=dfn[x])//目标结点low不小于当前结点dfn,当前结点是割点 
22                 pd=1;
23             lowu=min(lowu,lowv);//更新lowu 
24 }
25         else if(edges[i].to!=fa)//目标结点未访问过(返祖边) 
26             lowu=min(lowu,dfn[edges[i].to]);
27     if(x==fa&&child>1)//根节点的子树不少于2个 
28         ans[++num]=x;
29     else if(x!=fa&&pd)//非根节点至少有一个子树low值不小于当前结点的dfn值 
30         ans[++num]=x;
31     returnlowu;
32 }
33 void add(int x,int y)//邻接表存边 
34 {
35     edges[++tot].to=y;
36     edges[tot].pre=head[x];
37     head[x]=tot;
38 }
39 intmain()
40 {
41     intn,m;
42     scanf("%d%d",&n,&m);
43     for(int i=1;i<=m;i++)
44 {
45         intx,y;
46         scanf("%d%d",&x,&y);
47 add(x,y),add(y,x);
48 }
49     for(int i=1;i<=n;i++)//遍历n个点tarjan 
50         if(!dfn[i])
51 tarjan(i,i);
52     sort(ans+1,ans+1+num);
53     printf("%d
",num);
54     for(int i=1;i<=num;i++)
55         printf("%d ",ans[i]);
56     printf("");
57     return 0;
58 }
割点模板

洛谷的数据范围有毒!n,m不止10^5!大家打模板时要把数组开成10^6

免责声明:文章转载自《Tarjan求割点》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇c# 访问修饰符的访问权限使用Spring Mail发送QQ邮件下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

UVA 690 PipelineScheduling 位运算+dfs+剪枝

一开始最容易想到间隔最多为n,但是结点还是太多了,需要优化。 预处理:预判一下并保存下一个可以放的位置距离之前的距离。这样可以减少很多判断。 最优化剪枝:如果当前长度+剩下没放的程序*最短间隔如果大于等于ans,那么对答案没有贡献,可以剪去。 优化:占用和不占用两种状态,如果横向来看可以压缩为int,判断时用上为运算。 此题挂在长度的枚举上,我把长度为n给...

算法竞赛专题解析(6):搜索进阶(1)--搜索基础

本系列是这本算法教材的扩展:《算法竞赛入门到进阶》(京东当当) 清华大学出版社PDF下载地址:https://github.com/luoyongjun999/code 其中的“补充资料”如有建议,请联系:(1)QQ 群,567554289;(2)作者QQ,15512356 目录 1 搜索简介 2 搜索算法的基本思路 3 BFS的性质和代码实现 4...

Tarjan 算法详解

刚学的一个 新算法,终于有时间来整理一下了。 想必都对著名的 ‘太监’ 算法早有耳闻了吧。 TarjanTarjan 算法是一种求解有向图强连通分量的算法,它能做到线性时间的复杂度。 实现是基于DFS爆搜,深度优先搜索一张有向图。!注意!是有向图。然后根据树,堆栈,打标记等种种神奇扯淡方法来完成拆解一个图的工作。 如果要理解Tarjan,我们首先要了解一下...

TarJan 算法求解有向连通图强连通分量

[有向图强连通分量] 在有向图G中,如果两个 顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。 下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,...

USACO 1.3 Wormholes

Wormholes Farmer John's hobby of conducting high-energy physics experiments on weekends has backfired, causing N wormholes (2 <= N <= 12, N even) to materialize on his farm,...

图上的文章(割点和桥)

题外话: 今天不想码代码了,知识普及的一天 注意:以下内容是在无向图的基础上 无向图的割点很久之前就知道这些名词 今天终于可以来填坑了。。。 如果将连通图G中的某个点及和这个点相关的边删除后,将使连通分量数量增加,那么这个点就称为图G的割点或是接合点。 如果一个无向图没有割点,则这样的图被称为双连通图。 关于图的割点,有如下两条性质: 【性质一】 如果...