首页 > 其他分享 >点&边双连通分量

点&边双连通分量

时间:2023-02-17 19:23:38浏览次数:49  
标签:连通 边双 int 割点 dfn low 分量

双连通分量

参考博客:https://www.cnblogs.com/jiamian/p/11202189.html#_2

概念

双连通分量有点双连通分量和边双连通分量两种。若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。

点双连通和边双连通

在一张连通的无向图中,对于两个点 \(u\) 和 \(v\),如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说 \(u\) 和 \(v\) 边双连通。

在一张连通的无向图中,对于两个点 \(u\) 和 \(v\),如果无论删去哪个点(只能删去一个,且不能删 \(u\) 和 \(v\) 自己)都不能使它们不连通,我们就说 \(u\) 和 \(v\) 点双连通。

边双连通具有传递性,即,若 \(x\),\(y\) 边双连通,\(y\),\(z\) 边双连通,则 \(x\),\(z\) 边双连通。

点双连通:删掉一个点之后,图仍联通

边双连通:删掉一条边之后,图仍联通

概述

在一个无向图中,若任意两点间至少存在两条“点不重复”的路径,则说这个图是点双连通的(简称双连通, biconnected)

在一个无向图中,点双连通的极大子图称为点双连通分量(简称双连通分量, Biconnected Component,BCC)

性质

  1. 任意两点间至少存在两条点不重复的路径等价于图中删去任意一个点都不会改变图的连通性,即 BCC 中无割点。

  2. 若 BCC 间有公共点,则公共点为原图的割点。

  3. 无向连通图中割点一定属于至少两个 BCC,非割点只属于一个 BCC。

算法

在 Tarjan 过程中维护一个栈,每次 Tarjan 到一个结点就将该结点入栈,回溯时若目标结点 \(low\) 值不小于当前结点 \(dfn\) 值就出栈直到目标结点(目标结点也出栈),将出栈结点和当前结点存入 BCC。

点双连通分量

我们从割点的定义可以得知,当你把割点去掉的时候原来的一个强连通分量会变成两个或以上的强连通分量,但是我们可以知道,去掉割点以外的点对于连通块的数量是没有影响的,所以我们可以知道,割点加上他能分成的连通块的各个点可以组成一个点双连通分量,就像下面这个图:

image

一眼能看出来的就是 \(2\),\(3\) 都是割点,我们可以发现 \(3,6,7\) 和 \(0,1,2,3,4,5\) 都是点双连通分量。

我们根据上面提到的性质可以得知,割点至少在两个点双连通分量里,所以我们在弹栈存答案的时候不要把割点一起弹出,直到这个割点分出的连通块的点双连通分量都找完的时候再进行弹栈。

P8435 【模板】点双连通分量

code:

#include<bits/stdc++.h>
#define N 10001000
using namespace std;
struct sb{int u,v,next;}e[N];
int n,m,cnt,head[N],dfn[N],low[N],tot,stk[N],top,bcc;//bcc存放当前的点双连通分量的数量 
vector<int>ans[N];//存放答案 
inline void add(int u,int v)
{
	e[++cnt].u=u;
	e[cnt].v=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
inline void tarjan(int x,int fa)//fa是x的父节点 
{
	dfn[x]=low[x]=++tot;
	stk[++top]=x;
	int ch=0;//ch存放子节点的数量 
	for(int i=head[x];i;i=e[i].next)
	{
		int v=e[i].v;
		if(!dfn[v])//如果当前点还没有搜索过 
		{
			ch++;//子节点加一 
			tarjan(v,x);//继续往下搜 
			low[x]=min(low[x],low[v]);//正常更新low[x]的值 
			if(low[v]>=dfn[x])//割点的判定条件 
			{
				bcc++;//点双连通分量的数量加1 
				while(stk[top+1]!=v)//如果上一个弹出的栈顶元素不是v的话就一直弹 
					ans[bcc].push_back(stk[top--]);//将当前点放入栈中 
				ans[bcc].push_back(x);//最后把割点给加进去 
			}
		}
		else if(v!=fa)//不能用父节点来更新当前点的low值 
		  low[x]=min(low[x],dfn[v]);
	}
	if(fa==0&&ch==0)//特判只有一个点的情况 
	  ans[++bcc].push_back(x);
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])
		{
			top=0;
			tarjan(i,0);
		}
	}
	cout<<bcc<<endl;
	for(int i=1;i<=bcc;i++)
	{
		int siz=ans[i].size();
		cout<<siz<<" ";
		for(int j=0;j<siz;j++)
		  cout<<ans[i][j]<<" ";
		cout<<endl;
	}
	return 0;
}

另一种写法?
code:

void tarjan(int x,int fa)
{
	dfn[x]=low[x]=++tot;
	stk[++top]=x;
	if(x==fa&&head[x]==0) 
	{
		ans[++bcc].push_back(x);
		return;
	}
	int ch=0;
	for(int i=head[x];i;i=e[i].next)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tarjan(v,fa);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x])
			{
				ch++;
				if(x!=fa||ch>1)
					vis[x]=1;
				bcc++;
				int y;
				while(y!=v)
				{
					y=stk[top--];
					ans[bcc].push_back(y);
				}
				ans[bcc].push_back(x);
			}
		} 
		else if(x!=fa)
		  low[x]=min(low[x],dfn[v]);
	}
}

边双连通分量

割边,也就是桥,会找吧,代码就和缩点有一点不同,就是把 if(low[v]>=dfn[x]) 改成 if(low[v]>dfn[x])。
我们都知道把割边去掉之后就会多出一些强连通分量,如果要是不是割边的话,那就对连通块的数量没有影响,所以我们可以找出所有的割边,然后 dfs 一遍找出连通块。

来看下面这张图

image

可以看出里面不是割边的边只有 {1,3},{1,2},{2,3};我们可以发现一个 DCC 里面的边是没有割边的,所以我们把割边标记后 dfs 是正确的。

P8436 【模板】边双连通分量

code:

#include<bits/stdc++.h>
#define N 2000005
using namespace std;
int n,m,head[N],dfn[N],low[N],dcc,vis[N],tot,cnt=1;
struct sb{int u,v,next,flag;}e[N<<1];
vector<int>ans[N];
inline void add(int u,int v)
{
	e[++cnt].u=u;
	e[cnt].v=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
void tarjan(int x,int fa)
{
	dfn[x]=low[x]=++tot;
	for(int i=head[x];i;i=e[i].next)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tarjan(v,x);
			low[x]=min(low[x],low[v]);
			if(low[v]>dfn[x])
			  e[i].flag=e[i^1].flag=1;
		}
		else if(v!=fa)low[x]=min(low[x],dfn[v]);
	}
}
void dfs(int x)
{
	ans[dcc].push_back(x);
	vis[x]=1;
	for(int i=head[x];i;i=e[i].next)
	{
		int v=e[i].v;
		if(vis[v]||e[i].flag)continue;
		dfs(v);
	}
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;i++)
	  if(!dfn[i])
	    tarjan(i,0);
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])
		{
			dcc++;
			dfs(i);
		}
	}
	cout<<dcc<<endl;
	for(int i=1;i<=dcc;i++)
	{
		int siz=ans[i].size();
		cout<<siz<<" ";
		for(int j=0;j<siz;j++)
		  cout<<ans[i][j]<<" ";
		cout<<endl;
	}
	return 0;
}

标签:连通,边双,int,割点,dfn,low,分量
From: https://www.cnblogs.com/Multitree/p/17123735.html

相关文章

  • 记录一次交换机无法连通的思路错误路线
    场景:机房 交换机:TP-ST5008F CRS125-24G-1S-IN原有交换机坏了,要用新的交换机,将ST5008F带到机房后将光口相连遇到场景:1、交换机连服务器可以连通且网口会亮......
  • 强连通分量
    强连通分量定义连通图:图中,任意的两个点互相可达。强连通(\(strongly\connected\)):在有向图\(G\)中,若两个顶点间至少存在一条路径,称两个顶点强连通。强连通图:有向图......
  • bfs 实战-求连通分量个数
    bfs即广度优先搜索,等同于树的层序遍历,下面用一个题目来讲解题目:图的广度优先遍历问题描述已知无向图的邻接矩阵,以该矩阵为基础,给出广度优先搜索遍历序列,并且给出该无向......
  • 强连通分量例题
    1、BombProblem-5934(hdu.edu.cn)题意:二维平面图上,给一些炸弹的坐标(x,y)和炸弹可以引爆的范围圆的半径和引爆该炸弹的花费。问最少花费是多少可以把所有炸弹引爆?考......
  • tarjan求有向图强连通分量
    tarjan算法的简单应用hdu1269题目链接题意给定有向图,问改有向图是否只有一个强连通分量思路tarjan算法求有向图强连通分量的简单应用代码#include<iostream>......
  • 双连通分量
    点双连通poj1523题目链接题意给出无向图,求割点,并给出每个割点去掉后会形成几个分量思路tarjan,会形成几个分量注意根节点的不同即可代码#include<iostream>#i......
  • kosarajo求强连通分量
    kosarajo求强连通分量的证明因为根据反向图的dfs求出的拓扑序列使得原本的dag图中点的搜索优先级倒转所以在原图dfs会优先将最末端的点优先跑完,而上面的点再跑时,因为下......
  • Tarjan 强连通分量 板子
    #include<bits/stdc++.h>usingnamespacestd;constintN=10005;intn,m;intscc[N],siz[N],cnt;intdfn[N],low[N],tot;bitset<N>instk;stack<int>stk;......
  • Tarjan 算法 (图连通性)
    1.割边和割点首先我们dfs一遍构造出dfs树并排出dfn序.显然这棵树没有横叉边.考虑割边的形成条件.显然割边只能是树边,因为非树边会和对应的树上的路径组成环.......
  • 图的连通性问题
    \(dfs\)树及其他概念在这样一棵由在图上进行\(dfs\)所形成的\(dfs\)树中,我们注明了三种边。黑色的边为树边。绿色的边为前向边,由一个节点指向它子树上的节点。......