连通性问题(无向图)(未完结)

边双连通分量

我们首先定义两种边:返祖边为从一个点指向其祖先的边;横叉边从某个点指向树中另一个子树中的点的边。两者统称为非树边。而剩下的边即为树边,树边也就是再搜索树上的边。

考虑设 \(dfn_i\) 为点 \(i\) 是第几个被搜索到的,\(low_i\) 表示点 \(i\) 通过走若干条边之后,再走一条返祖边能到达的 \(dfn\) 最小的节点的 \(dfn\)

如果对于一个点 \(u\) 的儿子 \(v\),满足 \(low_v>dfn_u\),则 \(v\) 不经过与 \(u\) 之间的边,就跳不到 \(u\) 上面的点,所以这是一条割边。

代码:

void tar(int u,int fa){
	dfn[u]=low[u]=++tot;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(!dfn[j]){
			tar(j,i);
			if(low[j]>dfn[u]){
				bri[i]=bri[i^1]=1;
			}
			low[u]=min(low[u],low[j]);
		}
		else if(i!=(fa^1))low[u]=min(low[u],dfn[j]);
	}
}

接下来考虑一个事情,两个边双连通分量之间显然不能经过割边,于是我们跑一个 \(dfs\),从一个点出发不经过割边能到达的点就与其在一个边双连通分量内。

\(dfs\) 代码:

void dfs(int u,int c){
	col[u]=c;
	res[c].push_back(u);
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(col[j]||bri[i])continue;
		dfs(j,c);
	}
}

点双连通分量

如果对于一个点 \(u\) 的儿子 \(v\),满足 \(low_v\ge dfn_u\),且 \(u\) 不是这个连通分量的根或者有至少两个儿子,则 \(u\) 为割点。

求割点代码:

void tar(int u,int fa){
	dfn[u]=low[u]=++tot;
	int cnt=0;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(!dfn[j]){
			tar(j,fa);
			low[u]=min(low[u],low[j]);
			if(low[j]>=dfn[u]){
				cnt++;
				if(u!=fa||cnt>1)cut[u]=1;
			}
		}
		else low[u]=min(low[u],dfn[j]);
	}
}

首先注意,割点属于多个点双连通分量内。

我们考虑用一个栈记录之前经过的点,如果发现当前点 \(v\) 的父亲 \(u\) 是割点,则不断弹栈,直到把 \(v\) 弹出,弹出去的点和 \(u\) 则同属一个点双连通分量。

注意特判孤立点。

代码:

void tar(int u,int fa){
	dfn[u]=low[u]=++tot;
	stk[++top]=u;
	int son=0;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(!dfn[j]){
			son++;
			tar(j,u);
			low[u]=min(low[u],low[j]);
			if(low[j]>=dfn[u]){
				cnt++;
				int y;
				do{
					y=stk[top--];
					res[cnt].push_back(y);
				}while(stk[top+1]!=j);
				res[cnt].push_back(u);
			}
		}
		else{
			low[u]=min(low[u],dfn[j]);
		}
	}
	if(fa==0&&son==0)res[++cnt].push_back(u);
}
posted @ 2024-09-13 16:58  zxh923  阅读(1)  评论(0编辑  收藏  举报