点双连通分量&边双联通分量详解
文章目录点双连通分量前言概念性质找割点找点双附赠题表边双连通分量概念性质找割边找边双做法1做法2题表点双连通分量前言由于点双和边双都是无向图里面的东西,所以下面的讲解都以图是无向图作为前提。概念割点: 对于一个连通图中的点 xxx,假如删去这个点以及与所有 xxx 相连的边之后图不连通,那么称 xxx 为该图的割点。点双联通的: 对于一个无向图,假如仅仅对于该图而言其中不包含割点,那么......
点双连通分量
前言
由于点双和边双都是无向图里面的东西,所以下面的讲解都以图是无向图
作为前提。
概念
割点: 对于一个连通图中的点 x x x,假如删去这个点以及与所有 x x x 相连的边之后图不连通,那么称 x x x 为该图的割点。
点双联通的: 对于一个无向图,假如仅仅对于该图而言其中不包含割点,那么称这个图是点双连通的。(和网上的不一样?别急,往下看)
点双连通分量: 对于一个无向图中的极大点双连通的子图,我们称这个子图为点双连通分量。
为了方便,下面简称点双连通分量为点双。
性质
1、 除了一种比较特殊的点双,其他的点双都满足:任意两点间都存在至少两条点不重复路径。
比较特殊的点双:
网上大部分以该性质作为点双的定义,然后说这种图虽然不满足定义,但是是一个特殊的点双。而上面那个更严谨的定义则可以将这种情况包含在内,更加完备。
2、 图中任意一个割点都在至少两个点双中。
因为删去割点后图会不连通,所以割点至少连接着图的两部分,而由于点双中不能有割点,所以这两部分肯定不在同一个点双中,所以割点至少存在于两个点双中。
再次提醒,点双联通的的定义是 仅仅对于该图而言 其中不包含割点,那么称这个图是点双连通的,也就是说,可以包含原图中的割点。
3、 任意一个不是割点的点都只存在于一个点双中。
这个很显然,不多解释。
找割点
设 d f n [ x ] dfn[x] dfn[x] 表示遍历到 x x x 之前遍历过多少个点, l o w [ x ] low[x] low[x] 表示从 x x x 出发在不经过父子边的情况下能到达的最早的祖先的 d f n dfn dfn 值。
回看割点的定义:把这个点删除后图会不连通,那么肯定存在一些点,在不经过该割点的情况下无法到达割点的祖先,用这个性质来找就好了。
代码:
int dfn[maxn],low[maxn],id;
bool cut[maxn];
void dfs1(int x,int from)//from表示x与父亲之间的边的编号
{
dfn[x]=low[x]=++id; int son=0;
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==(from^1))continue;//不能通过反向边走回去
if(!dfn[y])
{
dfs1(y,x); son++;
if(low[y]<low[x])low[x]=low[y];//更新low,假如y能走到更小的,那么x也能走到
if(low[y]>=dfn[x])cut[x]=true;
//假如儿子在不经过父子边的情况下不能到达x的祖先,那么x就是个点
}
else if(dfn[y]<low[x])low[x]=dfn[y];//假如走到了一个dfn更小的,那么更新low
}
if(fa==-1&&son==1)cut[x]=false;//特判出发点在一个环中的情况
}
void tarjan()
{
for(int i=1;i<=n;i++)//如果图不连通,那么每一个连通块都要跑一遍
if(dfn[i]==0)dfs1(i,-1);
}
再说说那个特判,是用来处理这种情况的:
假如从红色节点出发,那么最后红色节点会被判定成割点,因为没有儿子能走到它的祖先,但是他压根就没有祖先啊,所以这时候,我们判一下,假如没有祖先,那么不能算割点,于是有了
fa==-1
这个判定条件。
但是
son==1
这个条件用来干啥?
我们注意到,出发点也有是割点的情况,比如:
如果从红色节点出发,而此时红色节点又刚好是割点,那怎么办呢?
这时候就要用到
son==1
这个限制了,因为如果起点是割点,那么 s o n son son 肯定大于 1 1 1。
发现只有遍历到一个 d f n [ y ] = 0 dfn[y]=0 dfn[y]=0 的 y y y 时, s o n son son 才会 + 1 +1 +1,所以 s o n son son 的实际意义就是该点所连接的点双数量。
而根据上面的性质,假如 s o n = = 1 son==1 son==1,那么这个点肯定不是割点,否则一定是割点,所以在 f a = − 1 fa=-1 fa=−1 的同时我们还需要进行 s o n = 1 son=1 son=1 这样的判断。
那可能有人会问了,为什么其他的割点不用 s o n = 1 son=1 son=1 这样的方法来判呢?仔细想想,其他的点和出发点的区别就是:他们都有父亲。而遍历的时候是不能往回走的,也就是说,我们不知道父亲的状态,所以不能用 s o n son son 来判。
找点双
这个时候就要请出伟大的 Tarjan 了!
我们依然考虑使用上面的 d f n dfn dfn 和 l o w low low 来求,我们将深搜时遇到的所有边加入到栈里面,当找到一个割点的时候,就将这个割点往下走到的所有边弹出,而这些边所连接的点就是一个点双了。
代码:
int dfn[maxn],low[maxn],id,t;
edge zhan[(maxn*maxn)<<1];//存边的栈
int belong[maxn],cnt;//belong记录每个点属于哪一个点双,cnt记录点双个数
bool cut[maxn];
set<int> s[maxn];//记录每个点双包含哪些点,如果题目不需要也可以不求
void dfs(int x,int from)
{
dfn[x]=low[x]=++id; int son=0;
for(int i=first[x];i;i=e[i].next)
{
if(i==(from^1))continue;
int y=e[i].y;
if(!dfn[y])
{
zhan[++t]=e[i]; dfs(y,i); son++;//先压栈再遍历
if(low[y]<low[x])low[x]=low[y];
if(low[y]>=dfn[x])//发现x是割点
{
cnt++; edge xx; cut[x]=true;
do{
xx=zhan[t--];//弹出
belong[xx.x]=belong[xx.y]=cnt;//标记
s[cnt].insert(xx.x);s[cnt].insert(xx.y);//记录
}while(xx.x!=x||xx.y!=y);//假如已经弹到 x到y 这条边了,就停下来
}
}
else if(dfn[y]<low[x])low[x]=dfn[y];
}
if(from==-1&&son==1)cut[x]=false;
}
附赠题表
Knights of the Round Table 题解
[HNOI2012]矿场搭建 题解
[ZJOI2004]嗅探器 题解
边双连通分量
概念
割边: 假如删去这条边后图不连通,那么称这条边为割边。
边双联通的: 对于一个图,假如仅仅对于该图而言其中没有割边,那么我们称这个图是边双联通的。
性质
1、 割边不属于任意边双,而其它非割边的边都属于且仅属于一个边双。
2、 对于一个边双中的任意两个点,它们之间都有至少两条边不重复的路径。
找割边
和找割点类似,直接上代码:
int dfn[maxn],low[maxn],id;
bool cut[maxn];
void dfs(int x,int from)
{
dfn[x]=low[x]=++id;
for(int i=first[x];i;i=e[i].next)
{
if(i==(from^1))continue;
int y=e[i].y;
if(!dfn[y])
{
dfs(y,i);
if(low[y]<low[x])low[x]=low[y];
if(low[y]>dfn[x])cut[i]=cut[i^1]=true;
//注意这里是大于而不是大于等于,原因想想就明白了
}
else if(dfn[y]<low[x])low[x]=dfn[y];
}
}
找边双
做法1
在请出Tarjan之前,我们先介绍另外一种做法:第一次 d f s dfs dfs 找出割边,然后第二次 d f s dfs dfs 在不经过割边的情况下遍历所有点,每一次遍历经过的一个子图就是一个边双。
做法2
用类似找点双的做法,但是栈里面压点,不压边。
代码:
int dfn[maxn],low[maxn],belong[maxn],id=0,cnt=0;
int zhan[maxn],t=0;
void dfs(int x,int from)
{
dfn[x]=low[x]=++id;zhan[++t]=x;
for(int i=first[x];i;i=e[i].next)
{
if(i==(from^1))continue;
int y=e[i].y;
if(!dfn[y])
{
dfs(y,i);
if(low[y]<low[x])low[x]=low[y];
}
else if(dfn[y]<low[x])low[x]=dfn[y];
}
if(dfn[x]==low[x])
{
cnt++; int xx;
do{
xx=zhan[t--];
belong[xx]=cnt;
}while(xx!=x);
}
}
题表
Caocao’s Bridges 题解
[CERC2015]Juice Junctions 题解
[USACO06JAN]冗余路径Redundant Paths 题解
一点个人感想
对于什么时候用点双,什么时候用边双这个问题,个人感觉是,点双的性质其实比边双要好,因为边双里面允许有割点的存在,边双只强调点集内所有点能相互到达,但点双就有一些更好的性质,比如像吃掉一个点之后剩下的点之间还能相互到达。
当然这些都是乱吹的,建议不看跳过即可。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)