无向图连通性问题

无向图连通性

定义

  • 割点:在无向图中,删去后使得连通分量增加的结点称为割点。
  • 割边:在无向图中,删去后使得连通分量增加的结边称为割边。
  • 点双连通图:不存在割点的无向连通图。
  • 边双连通图:不存在割边的无向连通图。

割点

Tarjan 算法时间复杂度 \(O(n+m)\),求全部的割点。

定义

定义 \(low_i\) 为结点 \(i\) 只经过非树边和子树边所能到达的最小 \(dfn_i\)

步骤

  1. 使用 dfs 求出 \(dfs_i\)\(low_i\)
  2. 若结点 \(x\) 为非根结点,即 \(x \neq root\) ,若 \(\exists E(x,y),dfn_x \leq low_y\),则 \(x\) 为割点
  3. \(x\) 为根节点,则至少有两条边满足以上性质时,才可以判定为割点。

代码实现

时间复杂度 \(O(n+m)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
vector<int> e[N];
int dfn[N],low[N],tot = 0,root;
bool vis[N];
int ans = 0;
void add(int x,int y){
	e[x].push_back(y);
	e[y].push_back(x);
}
void dfs(int x){
	dfn[x] = ++tot;
	low[x] = tot;
	int cnt = 0;
	for(int i=0;i<e[x].size();i++){
		int y = e[x][i];
		if(!dfn[y]){
			dfs(y);
			low[x] = min(low[x],low[y]);
			if(low[y] >= dfn[x]){
				cnt ++;
				if(x != root || cnt > 1){
					if(!vis[x]){
						ans ++;
						vis[x] = true;
					}
				}
			}	
		}else{
			low[x] = min(low[x],dfn[y]);
		}
	}
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			root = i;
			dfs(i);
		}
	} 
	printf("%d\n",ans);
	for(int i=1;i<=n;i++){
		if(vis[i]){
			printf("%d ",i);
		}
	}
}

割边

求割边的算法与割点类似,不同的是,割边存在的条件为 \(\exists E(x,y),dfn_x < low_y\)

时间复杂度 \(O(n+m)\)

边双连通

定义

  • 边双连通图:若连通无向图不存在割边,则成为边双连通图。
  • 边双连通分量 (e-DCC):无向图的极大双连通子图叫做边双连通分量。

步骤

我可以先 dfs 求出割边,再跑一遍不走割边的 dfs 求出连通分量即可。

代码实现

时间复杂度 \(O(n+m)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+5;
const int M = 2e6+5;
int head[N],nxt[M<<1],to[M<<1];
int dfn[N],low[N];
bool vis[M<<1],vvis[N];
vector<int> ans[N];
int cnt_ans = 0;
int tot = 1,cnt = 0,root;
void add(int x,int y){
	nxt[++tot] = head[x];
	to[tot] = y;
	head[x] = tot;
	nxt[++tot] = head[y];
	to[tot] = x;
	head[y] = tot;
}
void tarjon(int x,int last_edge){
	dfn[x] = low[x] = ++cnt;
	for(int i=head[x];i;i=nxt[i]){
		int y = to[i];
		if(!dfn[y]){
			tarjon(y,i);
			low[x] = min(low[x],low[y]);
			if(low[y] > dfn[x]){
				vis[i] = true;
				vis[i^1] = true;
			}
		}else if(i != (last_edge^1)){
			low[x] = min(low[x],dfn[y]);
		}
	}
}
void dfs(int x){
	ans[cnt_ans].push_back(x);
	for(int i=head[x];i;i=nxt[i]){
		int y = to[i];
		if(vis[i] || vvis[y]){
			continue;
		}
		vvis[y] = true;
		dfs(y);
	}
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			root = i;
			tarjon(i,-1);
		}
	}
	for(int i=1;i<=n;i++){
		if(!vvis[i]){
			cnt_ans ++;
			vvis[i] = true;
			dfs(i);
		}
	}
	printf("%d\n",cnt_ans);
	for(int i=1;i<=cnt_ans;i++){
		printf("%d ",ans[i].size());
		for(int j=0;j<ans[i].size();j++){
			printf("%d ",ans[i][j]);
		}
		puts("");
	}
}

点双连通

定义

  • 点双连通图:若连通无向图不存在割点,则成为点双连通图。
  • 点双连通分量 (v-DCC):无向图的极大双连通子图叫做点双连通分量。

步骤

深搜,用栈记录下访问的每一个节点,遇到割点,全部弹出栈,加上割点,构成 v-DCC,详细步骤如下:

  1. 遇到一个新的点,直接加入栈;
  2. 遇到 \(dfn_x\leq low_y\) 的情况,则将全部结点弹出,然后将 \(x\) 结点也加入答案,共同构成一个点双连通分量;
  3. 对于单独的点(即没有与其它点相连的点,或者只与割点相连的点),需要特判。

代码实现

时间复杂度 \(O(n+m)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+5;
vector<int> e[N],ans[N];
stack<int> s;
int dfn[N],low[N];
int tot = 0,root,cnt_ans = 0;
void add(int x,int y){
	e[x].push_back(y);
	e[y].push_back(x);
}
void tarjon(int x){
	dfn[x] = low[x] = ++tot;
	int son = 0;
	s.push(x);
	for(int i=0;i<e[x].size();i++){
		int y = e[x][i];
		if(!dfn[y]){
			son ++;
			tarjon(y);
			low[x] = min(low[x],low[y]);
			if(low[y] >= dfn[x]){
				cnt_ans ++;
				int t;
				do{
					t = s.top();
					s.pop();
					ans[cnt_ans].push_back(t);
				}while(t != y && s.size());
				ans[cnt_ans].push_back(x);
			}
		}else{
			low[x] = min(low[x],dfn[y]);
		}
	}
	if(x == root && son == 0){
		cnt_ans ++;
		ans[cnt_ans].push_back(x);
		return;
	}
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			while(s.size()){
				s.pop();
			}
			root = i;
			tarjon(i);
		}
	}
	printf("%d\n",cnt_ans);
	for(int i=1;i<=cnt_ans;i++){
		printf("%d ",ans[i].size());
		for(int j=0;j<ans[i].size();j++){
			printf("%d ",ans[i][j]);
		}
		puts("");
	}
	return 0;
}

圆方树

对于一个图,将其中的点双连通分量抽象为一个方点,其余的点为圆点,将每一个点双连通子图连接为一个以所对应的方点为中心的菊花图,再将相连的原点连接,形成的树叫做圆方树。

性质

  • 圆方树连通,当且仅当原图连通。
  • 每一个方点连接的点为圆点,反之亦然。
  • 圆方树的结点个数可能为 \(\left[N,2N\right]\)
posted @ 2023-12-23 17:05  WhileTureRP++  阅读(20)  评论(0编辑  收藏  举报