图的连通性问题

前言:2023.2.2-2.3第一次接触,被打得落花流水,如今值2023.5.14——入坑 \(OI\) 9个月祭&&距离退役 \(1\) 年祭,回首连通性问题,柳暗花明,豁然开朗。时间果然能抚平一切。


主要思想:Tarjan算法。


Tarjan算法

主要为记录两个数组 \(dfn_i\)\(low_i\)

\(dfn_i\) 表示将图进行 dfs 遍历的时间戳(遍历到的顺序)。

\(low_i\) 表示 \(min(\)这个点为根的 dfs 生成树的子树中节点的 \(low_i\) 的最小值,非其子树点中能仅通过一条非树边到达他的点的 \(dfn_i\) 的最小值 \()\)

于是对于与 \(u\) 连边的节点 \(v\)

\(v\)\(u\) 子树上的点,则 \(low_u=min(low_u,low_v)\)

否则若不是,则 \(low_u=min(low_u,dfn_v)\)

Tarjan算法的主要流程便是通过 dfs 的方式在标记每个遍历到的点的 \(dfn_i\) 的同时,对 \(low_i\) 进行赋值,同时根据不同的题目要求求解不同的连通分量/割点/割边等等。

通常判断要点为:

\(v\)\(u\) 在搜索树上的儿子的情况下,若 $low_v>dfn_u $ ,则……


边的双连通问题

\(v\)\(u\) 在搜索树上的儿子的情况下,若 $low_v>dfn_u $,则判断这条边为割边

简略证明:若 \(u\) 的儿子 \(v\) 不能通过一条非树边回到 \(u\) 以上的节点,则意味着 \(u\)\(v\) 仅有一条路径,即这条边为割边。

根据以上结论,则可找到其双连通分量。

核心代码:

inline void tar(ll x,ll fa) {
	dfn[x]=low[x]=++num;
	for (ll i=head[x];i;i=nextx[i]) {
		ll y=ver[i];
		if (!dfn[y]) {
			tar(y,i);
			low[x]=min(low[x],low[y]);
			if (low[y]>dfn[x]) bri[i]=bri[i^1]=1;
		}
		else if (i!=(fa^1)) { //若这条边不是上一条边的反向边
			low[x]=min(low[x],dfn[y]);
		}
	}
}

inline void dfs(ll x) {
	c[x]=1;
	ans[dcc].push_back(x);
	for (ll i=head[x];i;i=nextx[i]) {
		ll y=ver[i];
		if (c[y]||bri[i]) continue; 
		dfs(y);
	}
}

这里注意,求点双的时候尽量不要在 tarjan 里直接求,原因如此

模板题:边双连通分量


点双联通问题

1.\(v\)\(u\) 在搜索树上的儿子的情况下,若 $low_v \ge dfn_u $,则判断这条边为割点

简略证明:若 \(u\) 的儿子 \(v\) 通过一条非树边最高仅能到达 \(u\),则意味着 $v $ 想要到达 \(u\) 以上必须要通过 \(u\),即 \(u\) 是割点。

2.若 \(u\) 为根点且有超过两棵子树的情况下,\(u\) 一定为割点。

简略证明:若满足上述条件,把 \(u\) 去掉后他的子树一定会分离。

根据以上结论,则可找到其点双连通分量。

核心代码:

inline void tar(ll x,ll fa) {
	dfn[x]=low[x]=++num;
	s.push(x);
	if (head[x]==0&&fa==0) ans[++dcc].push_back(x);
	for (ll i=head[x];i;i=nextx[i]) {
		ll y=ver[i];
		if (!dfn[y]) {
			tar(y,x);
			low[x]=min(low[x],low[y]);
			if (low[y]>=dfn[x]) {
				++dcc;
				ll T;
				while (T!=y) {
					T=s.top();
					s.pop();
				   ans[dcc].push_back(T);
				   color[T]=dcc;
				    
				}
				ans[dcc].push_back(x);
			}
		}
		else low[x]=min(low[x],dfn[y]);
	}
 	
}

模板题:割点点双连通分量


强连通性问题

\(dfn_u=low_u\),则这个点为当前强连通分量的根,意味着直到下一个 \(dfn_u'=low_u'\),这之间的所有点都为以 \(u\) 为根的强连通分量

简略证明:若当前点的 \(dfn_u \ne low_u\),则它一定可以到达除了它连边以外的其他点,则他一定在某个连通分量里。

核心代码:

inline void solve(ll u) {
	q.pop();
	each[tot]++,vis[u]=0;
}
inline void tarjan(ll u) {
	vis[u]=dfn[u]=low[u]=++cnt;
	q.push(u);
	for (ll i=0;i<G[u].size();++i) {
		ll 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],dfn[v]);
	}
	if (dfn[u]==low[u]) {
		++tot;
		while (q.top()!=u) solve(q.top());
		solve(u);
	}
}

模板题:奶牛舞会(模板)强连通分量的应用缩点

有用的结论:强连通图每个点一定都有出度和入度,但每个点都有出度和入度的有向无环图不一定是强连通图。


圆方树

构造:将一个无向连通图的所有点双拆开后,增加一个方点将其与这个点双内所有的点连边。

一个结论:每一对相邻且连通的点双之间一定有公共点,这个点即是两个点双之间的割点。

证明:两个相邻且联通的点双一定会有割点将其分离,而这个割点一定与两个点双共点。

由上面的结论可推出(广义)圆方树的性质之一:

圆方树上的圆点和方点一定交替出现。

同时一个点是割点的充要条件是:这个点在圆方树上的度数一定大于 \(1\)

例题:铁人两项

posted @ 2023-06-06 18:23  Pwtking  阅读(79)  评论(2)    收藏  举报