20250818 割点 割边 点双总结
引子
这是一个引子 ^ _ ^
连通分量
首先,我们知道子图就是从一张图里选出几个点和几条边,如果子图里面的各个点互相连通,就叫连通子图,连通子图的点和边数量尽可能大就是极大连通子图,注意,是极大,我们把极大连通子图叫做连通分量,也叫连通块。
所以一张图里连通分量的数量是有限的,并且唯一,下面来举个栗子:
以上图有两个连通分量,分别为{1,2,3,4,5}和{6,7}。
割点
在一张图里,去掉一个点,如果连通分量数量增加了,说明这个点是割点,也就是说这个点原来所处的连通分量不连通了,分裂成了更多的连通分量。
有一种蒻的不能再蒻的算法,也就是把每个点假设为割点,看把这个点去掉后连通分量是否增加了。当然,我们通常用tarjan算法来找割点。
实现
tarjan算法在dfs过程中动态维护dfn和low两个数组。其中low表示节点在不经过搜索树父节点情况下能到达的最小时间戳。
判断割点的核心条件:
- 对于非根节点u,若存在子节点v满足lowv≥dfnulow_v \geq dfn_ulowv≥dfnu,则u是割点。该条件表明删除u后,v所在子树无法通过其他路径回到u的祖先节点。
- 对于根节点,需要特殊处理:若其在搜索树中有两个及以上子节点,则为割点;否则不是割点。
证明思路:
- 当根节点只有一个子节点时,说明整个连通分量可以通过单次搜索遍历完成,删除根节点不会增加连通分量数量。
- 当根节点有多个子节点时,说明各子树之间没有其他连接路径,删除根节点将导致各子树相互隔离,从而增加连通分量数量。
这种判断方法确保了割点判定的正确性和高效性。
tarjan时间复杂度O(n+m)。
vector<int>E[20005];
int low[20005],dfn[20005],n,m,k;
bool cut[20005];
void tarjan(int x,int f){int s=0;k++;dfn[x]=low[x]=k;for(int i=0;i<E[x].size();i++){int v=E[x][i];if(!dfn[v]){tarjan(v,f);low[x]=min(low[x],low[v]);if(low[v]>=dfn[x]&&x!=f){cut[x]=1;}if(x==f){s++;}}else{low[x]=min(dfn[v],low[x]);}}if(x==f&&s>=2){cut[x]=1;}
}
割边
和割点同理,只是从去掉一个点变成了去掉一条边。
大致和割点都差不多,只是判断条件不一样,改成了lowv>dfnulow_v > dfn_ulowv>dfnu。
时间复杂度当然相同。
实现
vector<int>E[155];
vector<edge>e;
int low[155],dfn[155],n,m,k;
void dfs(int x,int f){dfn[x]=low[x]=++k;for(int i=0;i<E[x].size();i++){int v=E[x][i];if(v!=f){if(!dfn[v]){dfs(v,x);low[x]=min(low[x],low[v]);if(low[v]>dfn[x]){e.push_back({min(x,v),max(x,v)});}}else{low[x]=min(dfn[v],low[x]);}}}
}
点双连通分量
任意删除一个点后仍保持连通的连通分量称为点双连通分量,简称点双。
- 任意两个点双之间至多有一个公共点,该点必为割点
- 在搜索树中,每个点双的dfn的最小点必定是割点或根节点
只要用一个栈记录已访问顶点,当回溯到割点时提取对应的点双连通分量就行了。
int s[M],low[N],dfn[N],fir[N],nxt[M],to[M],idx,n,m,top,bcc;
vector<int>ans[N];
void tarjan(int u,int fa){int son=0;low[u]=dfn[u]=++idx;s[++top]=u;for(int i=fir[u];i;i=nxt[i]){int v=to[i];if(!dfn[v]){son++;tarjan(v,u);low[u]=min(low[u],low[v]);if(low[v]>=dfn[u]){bcc++;while(s[top + 1]!=v){ans[bcc].push_back(s[top--]);}ans[bcc].push_back(u);}}else if(v!=fa){low[u]=min(low[u],dfn[v]);}}if(fa==0&&son==0){ans[++bcc].push_back(u);}
}