tarjan求强连通分量+缩点+割点以及一些证明

时间:2022-09-22 11:12:13

“tarjan陪伴强联通分量

生成树完成后思路才闪光

欧拉跑过的七桥古塘

让你 心驰神往”----《膜你抄》

 

自从听完这首歌,我就对tarjan开始心驰神往了,不过由于之前水平不足,一直没有时间学习。这两天好不容易学会了,写篇博客,也算记录一下。

 

一、tarjan求强连通分量

1、什么是强连通分量?

引用来自度娘的一句话:

“有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。”

一脸懵逼......不过倒也不难理解。

反正就是在图中找到一个最大的图,使这个图中每个两点都能够互相到达。这个最大的图称为强连通分量,同时一个点也属于强连通分量。

tarjan求强连通分量+缩点+割点以及一些证明

如图中强连通分量有三个:1-2-3,4,5

 

2、强连通分量怎么找?

噫......当然,通过肉眼可以很直观地看出1-2-3是一组强连通分量,但很遗憾,机器并没有眼睛,所以该怎么判断强连通分量呢?

如果仍是上面那张图,我们对它进行dfs遍历。

tarjan求强连通分量+缩点+割点以及一些证明

可以注意到红边非常特别,因为如果按照遍历时间来分类的话,其他边都指向在自己之后被遍历到的点,而红边指向的则是比自己先被遍历到的点。

 

如果存在这么一条边,那么我们可以yy一下,emmmm.......

从一个点出发,一直向下遍历,然后忽得找到一个点,那个点竟然有条指回这一个点的边!

那么想必这个点能够从自身出发再回到自身

想必这个点和其他向下遍历的该路径上的所有点构成了一个环,

想必这个环上的所有点都是强联通的。

但只是强联通啊,我们需要求的可是强连通分量啊......

 

那怎么办呢?

我们还是yy出那棵dfs树

不妨想一下,什么时候一个点和他的所有子孙节点中的一部分构成强连通分量?

他的子孙再也没有指向他的祖先的边,却有指向他自己的边

因为只要他的子孙节点有指向祖先的边,显然可以构成一个更大的强联通图。

 tarjan求强连通分量+缩点+割点以及一些证明

比如说图中红色为强连通分量,而蓝色只是强联通图

 

那么我们只需要知道这个点u下面的所有子节点有没有连着这个点的祖先就行了。

但似乎还有一个问题啊......

 

我们怎么知道这个点u它下面的所有子节点一定是都与他强联通的呢?

这似乎是不对的,这个点u之下的所有点不一定都强联通

tarjan求强连通分量+缩点+割点以及一些证明

那么怎么在退回到这个点的时候,知道所有和这个点u构成强连通分量的点呢?

开个栈记录就行了

什么?!这么简单?

没错~就是这么简单~

如果在这个点之后被遍历到的点已经能与其下面的一部分点(也可能就只有他一个点)已经构成强连通分量,即它已经是最大的。

那么把它们一起从栈里弹出来就行了。

所以最后处理到点u时如果u的子孙没有指向其祖先的边,那么它之后的点肯定都已经处理好了,一个常见的思想,可以理解一下。

所以就可以保证栈里留下来u后的点都是能与它构成强连通分量的。

 

似乎做法已经明了了,用程序应该怎么实现呢?

 

所以为了实现上面的操作,我们需要一些辅助数组

(1)、dfn[ ],表示这个点在dfs时是第几个被搜到的。

(2)、low[ ],表示这个点以及其子孙节点连的所有点中dfn最小的值

(3)、stack[ ],表示当前所有可能能构成是强连通分量的点。

(4)、vis[ ],表示一个点是否在stack[ ]数组中。

那么按照之上的思路,我们来考虑这几个数组的用处以及tarjan的过程。

 

假设现在开始遍历点u:

 

(1)、首先初始化dfn[u]=low[u]=第几个被dfs到

dfn可以理解,但为什么low也要这么做呢?

 因为low的定义如上,也就是说如果没有子孙与u的祖先相连的话,dfn[u]一定是它和它的所有子孙中dfn最小的(因为它的所有子孙一定比他后搜到)。

 

(2)、将u存入stack[ ]中,并将vis[u]设为true

stack[ ]有什么用?

如果u在stack中,u之后的所有点在u被回溯到时u和栈中所有在它之后的点都构成强连通分量。

 

(3)、遍历u的每一个能到的点,如果这个点dfn[ ]为0,即仍未访问过,那么就对点v进行dfs,然后low[u]=min{low[u],low[v]}

low[ ]有什么用?

应该能看出来吧,就是记录一个点它最大能连通到哪个祖先节点(当然包括自己)

如果遍历到的这个点已经被遍历到了,那么看它当前有没有在stack[ ]里,如果有那么low[u]=min{low[u],low[v]}

如果已经被弹掉了,说明无论如何这个点也不能与u构成强连通分量,因为它不能到达u

如果还在栈里,说明这个点肯定能到达u,同样u能到达他,他俩强联通。

 

(4)、假设我们已经dfs完了u的所有的子树那么之后无论我们再怎么dfs,u点的low值已经不会再变了。

那么如果dfn[u]=low[u]这说明了什么呢?

再结合一下dfn和low的定义来看看吧

dfn表示u点被dfs到的时间,low表示u和u所有的子树所能到达的点中dfn最小的。

这说明了u点及u点之下的所有子节点没有边是指向u的祖先的了,即我们之前说的u点与它的子孙节点构成了一个最大的强连通图即强连通分量

此时我们得到了一个强连通分量,把所有的u点以后压入栈中的点和u点一并弹出,将它们的vis[ ]置为false,如有需要也可以给它们打上相同标记(同一个数字)

 

tarjan到此结束

至于手模?tan90°!网上有不少大佬已经手摸了不少样例了,想必不需要本蒟蒻再补充了。

 

结合上面四步代码已经可以写出了:

tarjan求强连通分量+缩点+割点以及一些证明

对了,tarjan一遍不能搜完所有的点,因为存在孤立点或者其他

所以我们要对一趟跑下来还没有被访问到的点继续跑tarjan

怎么知道这个点有没有被访问呢?

看看它的dfn是否为0!

tarjan求强连通分量+缩点+割点以及一些证明

这看起来似乎是o(n^2)的复杂度,但其实均摊下来每个点只会被遍历一遍

所以tarjan的复杂度为o(n)。

 

来一道例题吧,这是模板题,应该做到提交框AC

[USACO06JAN]牛的舞会The Cow Prom

 给你n个点,m条边,求图中所有大小大于1的强连通分量的个数

输入样例#1:
5 4
2 4
3 5
1 2
4 1
输出样例#1: 
1

显然是tarjan水题,数出强连通分量的个数,给每个强连通分量的点染色,统计出每个强连通分量中点的个数,如果大于一,则答案加一。

代码:

#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f vector<int> g[];
int color[],dfn[],low[],stack[],vis[],cnt[];
int deep,top,n,m,sum,ans; void tarjan(int u)
{
dfn[u]=++deep;
low[u]=deep;
vis[u]=;
stack[++top]=u;
int sz=g[u].size();
for(int i=;i<sz;i++)
{
int v=g[u][i];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else
{
if(vis[v])
{
low[u]=min(low[u],low[v]);
}
}
}
if(dfn[u]==low[u])
{
color[u]=++sum;
vis[u]=;
while(stack[top]!=u)
{
color[stack[top]]=sum;
vis[stack[top--]]=;
}
top--;
}
} int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<=m;i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
}
for(int i=;i<=n;i++)
{
if(!dfn[i])
{
tarjan(i);
}
}
for(int i=;i<=n;i++)
{
cnt[color[i]]++;
}
for(int i=;i<=sum;i++)
{
if(cnt[i]>)
{
ans++;
}
}
printf("%d\n",ans);
}

 

二、tarjan缩点

其实这也是利用了tarjan求强连通分量的方法,对于一些贡献具有传导性,比如友情啊、路径上的权值啊等等。

思想就是因为强连通分量中的每两个点都是强连通的,可以将一个强连通分量当做一个超级点,而点权按题意来定。

tarjan求强连通分量+缩点+割点以及一些证明

tarjan求强连通分量+缩点+割点以及一些证明

 

来看一道题吧。

poj2186 Popular Cows

告诉你有n头牛,m个崇拜关系,并且崇拜具有传递性,如果a崇拜b,b崇拜c,则a崇拜c,求最后有几头牛被所有牛崇拜。

 

Sample Input
3 3
1 2
2 1
2 3
Sample Output
1

 

显然一个强联通分量内的所有点都是满足条件的,我们可以对整张图进行缩点,然后就简单了。

剩下的所有点都不是强连通的,现在整张图就是一个DAG(有向无环图)

那么就变成一道水题了,因为这是一个有向无环图,不存在所有点的出度都不为零的情况。

所以必然有1个及以上的点出度为零,如果有两个点出度为零,那么这两个点肯定是不相连的,即这两圈牛不是互相崇拜的,于是此时答案为零,如果有1个点出度为0,那么这个点就是被全体牛崇拜的,

这个点可能是一个强联通分量缩成的超级点,所以应该输出整个强联通分量中点的个数。

代码:

#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; int dfn[],low[],vis[],stack[],color[],du[],cnt[];
int n,m,top,sum,deep,tmp,ans;
vector<int> g[]; void tarjan(int u)
{
dfn[u]=low[u]=++deep;
vis[u]=;
stack[++top]=u;
int sz=g[u].size();
for(int i=; i<sz; i++)
{
int v=g[u][i];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else
{
if(vis[v])
{
low[u]=min(low[u],low[v]);
}
}
}
if(dfn[u]==low[u])
{
color[u]=++sum;
vis[u]=;
while(stack[top]!=u)
{
color[stack[top]]=sum;
vis[stack[top--]]=;
}
top--;
}
} int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(vis,,sizeof(du));
memset(vis,,sizeof(low));
memset(dfn,,sizeof(dfn));
memset(vis,,sizeof(vis));
memset(vis,,sizeof(cnt));
memset(vis,,sizeof(color));
memset(vis,,sizeof(stack));
for(int i=; i<=n; i++)
{
g[i].clear();
}
for(int i=; i<=m; i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
}
for(int i=; i<=n; i++)
{
if(!dfn[i])
{
tarjan(i);
}
}
for(int i=; i<=n; i++)
{
int sz=g[i].size();
for(int j=; j<sz; j++)
{
int v=g[i][j];
if(color[v]!=color[i])
{
du[color[i]]++;
}
}
cnt[color[i]]++;
}
for(int i=; i<=sum; i++)
{
if(du[i]==)
{
tmp++;
ans=cnt[i];
}
}
if(tmp==)
{
printf("0\n"); }
else
{
if(tmp>)
{
printf("0\n");
}
else
{
printf("%d\n",ans);
}
}
}
}

三、tarjan求割点、桥

1、什么是割点、桥

再来引用一遍度娘:

在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,就称这个点集为割点集合。

又是一脸懵逼。。。。

总而言之,就是这个点维持着双联通的继续,去掉这个点,这个连通分量就无法在维持下去,分成好几个连通分量。

tarjan求强连通分量+缩点+割点以及一些证明

比如说上图红色的即为一个割点。

桥:

如果一个无向连通图的边连通度大于1,则称该图是边双连通的 (edge biconnected),简 称双连通或重连通。一个图有桥,当且仅当这个图的边连通度为 1,则割边集合的唯一元素 被称为桥(bridge),又叫关节边(articulationedge)。一个图可能有多个桥。(该资料同样来自百度)

对于连通图有两种双联通,边双和点双,桥之于边双如同割点之于点双

tarjan求强连通分量+缩点+割点以及一些证明

如图则是一个桥。

2、割点和桥怎么求?

与之前强连通分量中的tarjan差不多。但要加一个特判,根节点如果有两个及以上的儿子,那么他也是割点。

 tarjan求强连通分量+缩点+割点以及一些证明

模板题:洛谷3388

求割点的个数和数量

代码:

#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define hi printf("hi!");
using namespace std; vector<int> g[];
int dfn[],low[],iscut[],son[];
int deep,root,n,m,ans; int tarjan(int u,int fa)
{
int child=,lowu;
lowu=dfn[u]=++deep;
int sz=g[u].size();
for(int i=;i<sz;i++)
{
int v=g[u][i];
if(!dfn[v])
{
child++;
int lowv=tarjan(v,u);
lowu=min(lowu,lowv);
if(lowv>dfn[u])
{
iscut[u]=;
}
}
else
{
if(v!=fa&&dfn[v]<dfn[u])
{
lowu=min(lowu,dfn[v]);
}
}
}
if(fa<&&child==)
{
iscut[u]=false;
}
low[u]=lowu;
return lowu;
} int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<=m;i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
for(int i=;i<=n;i++)
{
if(!dfn[i])
{
root=i;
tarjan(i,-);
}
}
for(int i=;i<=n;i++)
{
if(iscut[i])
{
ans++;
}
}
printf("%d\n",ans);
for(int i=;i<=n;i++)
{
if(iscut[i])
{
printf("%d ",i);
}
}
}

桥的求法也差不多

 tarjan求强连通分量+缩点+割点以及一些证明

并没有找到模板题目,所以只好把没检验过的代码放着了......如有错误还请留言指正

#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define hi printf("hi!");
using namespace std; vector<pair<int,int> >bridge;
vector<int> g[];
int dfn[],low[];
int deep,root,n,m,ans; int tarjan(int u,int fa)
{
int lowu;
lowu=dfn[u]=++deep;
int sz=g[u].size();
for(int i=;i<sz;i++)
{
int v=g[u][i];
if(!dfn[v])
{
int lowv=tarjan(v,u);
lowu=min(lowu,lowv);
if(lowv>dfn[u])
{
int from,to;
from=u;
to=v;
if(from>to)
{
swap(from,to);
}
bridge.push_back(make_pair(from,to));
}
}
else
{
if(v!=fa&&dfn[v]<dfn[u])
{
lowu=min(lowu,dfn[v]);
}
}
}
low[u]=lowu;
return lowu;
} int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<=m;i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
for(int i=;i<=n;i++)
{
if(!dfn[i])
{
root=i;
tarjan(i,-);
}
}
for(int i=;i<bridge.size();i++)
{
printf("%d %d\n",bridge[i].first,bridge[i].second);
}
}

 

 

 

 

おわり

tarjan求强连通分量+缩点+割点以及一些证明的更多相关文章

  1. tarjan求强连通分量&plus;缩点&plus;割点&sol;割桥(点双&sol;边双)以及一些证明

    “tarjan陪伴强联通分量 生成树完成后思路才闪光 欧拉跑过的七桥古塘 让你 心驰神往”----<膜你抄>   自从听完这首歌,我就对tarjan开始心驰神往了,不过由于之前水平不足,一 ...

  2. HDU 1827 Summer Holiday(tarjan求强连通分量&plus;缩点构成新图&plus;统计入度&plus;一点贪心思)经典缩点入门题

    Summer Holiday Time Limit: 10000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)T ...

  3. 【BZOJ1051】1051&colon; &lbrack;HAOI2006&rsqb;受欢迎的牛 tarjan求强连通分量&plus;缩点

    Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎. 这种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认 ...

  4. Tarjan求强连通分量 缩点

    强连通分量的定义: 在一张有向图中,如果两个点u,v之间能相互到达则称这两个点u,v是强连通的,在这个基础上如果有向图G中的任意两个顶点都强连通,那么称图G是一个强连通图.有向非强连通图的极大强连通子 ...

  5. tarjan求强连通分量&plus;缩点 模板

    #define N 100100 #define M 200200 int n,m; int id,index; //id表示缩点后点的id,index表示进行tarjan算法时访问的点先后 int ...

  6. Tarjan求强连通分量,缩点,割点

    Tarjan算法是由美国著名计算机专家发明的,其主要特点就是可以求强连通分量和缩点·割点. 而强联通分量便是在一个图中如果有一个子图,且这个子图中所有的点都可以相互到达,这个子图便是一个强连通分量,并 ...

  7. Tarjan求强连通分量、求桥和割点模板

    Tarjan 求强连通分量模板.参考博客 #include<stdio.h> #include<stack> #include<algorithm> using n ...

  8. UESTC 901 方老师抢银行 --Tarjan求强连通分量

    思路:如果出现了一个强连通分量,那么走到这个点时一定会在强连通分量里的点全部走一遍,这样才能更大.所以我们首先用Tarjan跑一遍求出所有强连通分量,然后将强连通分量缩成点(用到栈)然后就变成了一个D ...

  9. CCF 高速公路 tarjan求强连通分量

    问题描述 某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路. 现在,大臣们帮国王拟了一个修高速公路的 ...

随机推荐

  1. 利用 Html 元标记控制搜索引擎蜘蛛

    摘要:快照不被百度缓存: meta name = Baiduspider content = noarchive 所有搜索引擎,抓取这个页面.爬行链接.禁止快照: meta name = robots ...

  2. Doodle Poll 投票文档

    使用Doodle Poll网页文件可以让大家投票看什么时间大家都合适.

  3. Apache Spark源码走读之3 -- Task运行期之函数调用关系分析

    欢迎转载,转载请注明出处,徽沪一郎. 概要 本篇主要阐述在TaskRunner中执行的task其业务逻辑是如何被调用到的,另外试图讲清楚运行着的task其输入的数据从哪获取,处理的结果返回到哪里,如何 ...

  4. Wix&colon; Using Patch Creation Properties - Small Update

    Source Reference: wix help document  -- WiX Toolset License Using Patch Creation Properties  A patch ...

  5. mysqltuner

    http://mysqltuner.com/ MySQLTuner-perl MySQLTuner is a script written in Perl that will assist you w ...

  6. cf466C Number of Ways

    C. Number of Ways time limit per test 2 seconds memory limit per test 256 megabytes input standard i ...

  7. vue 初识组件

    Vue.component("greeting",{ template: `<p>{{ name }}大家好 <button v-on:click="c ...

  8. Java动态调用类中方法

    在Java中,调用类的方法有两种方式:对于静态方法可以直接使用类名调用,对于非静态方法必须使用类的对象调用.反射机制提供了比较另类的调用方式,可以根据需要指定要调用的方法,而不必在编程时确定.调用的方 ...

  9. 201621123037 《Java程序设计》第10周学习总结

    作业10-异常 标签(空格分隔): Java 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 2. 书面作业 本次PTA作业题集异常 1. 常用异常 结合题集题目7 ...

  10. BZOJ 1924 所驼门王的宝藏&lpar;强连通分量缩点&plus;DAG最长链&rpar;

    思路不是很难,因为宝藏只会在给出的n个点内有,于是只需要在这n个点里面连边,一个点如果能到达另一个点则连一条有向边, 这样用强连通分量缩点后答案就是DAG的最长链. 问题在于暴力建图是O(n^2)的, ...