割点
定义:什么是割点
在无向图中,所有能联通的点构成了一个“
联通分量
”。在一个联通分量中有一些关键点,如果删除它,会把这个联通分量分成两个或更多
。这种点称为割点
寻找:判断割点的方法——DFS求割点的算法
首先是口胡原理:
在一个
联通分量G
中,对任意点
a做DFS(能访问所有节点),构成一棵“深搜优先生成树T”
,
定理一:T的根节点s
是割点
,当且仅当
s有两个或更多的子节点时成立
。
理解一:如果s是割点,它会把图分成不相连的几部分,这几个部分都会生成子树;如果s不是割点,它只会连接一个子树。
定理二:T的非根节点u
是割点,`当且仅当u存在一个子节点v,v及其后代都没有回退边连回u的任意祖先。
理解二:如果u是割点,它会把图分为两部分或更多,其中至少一个后代肯定没有通过其他边(回退边,绕过u回去的边)回到u的某祖先,否则图不会被分开。
>多看看书吧……
然后是代码实现:
定义:
u的一个直接后代是v
num[u]
:记录DFS对每个点的访问顺序
low[v]
:记录v与v的后代能连回到的深度最低祖先的num
只要low[v]>=num[u]
就说明在v这个支路上没有回退边连回u的祖先,最多回到u本身。根据定理二,可以知道u是一个割点。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int n, m, dfn[N], cnt, low[N];
int e[M], ne[M], h[N], idx;
bool iscut[N];
void dfs(int u, int root) {
low[u] = dfn[u] = ++ cnt;
int child = 0;
for(int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if(!dfn[v]) {//没搜过,说明是树上的节点
// if(u == root)
child ++;
dfs(v, root);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u] && u != root)
iscut[u] = 1;
}
else //搜过,说明不是树上的节点,是回边。
low[u] = min(low[u], dfn[v]);
}
if(u == root && child >= 2) iscut[root] = 1;
}
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i ++) {
int a, b; scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
for(int i = 1; i <= n; i ++)
if(!dfn[i])
dfs(i, i);
int ans = 0;
for(int i = 1; i <= n; i ++)
if(iscut[i])
ans ++;
printf("%d\n", ans);
for(int i = 1; i <= n; i ++)
if(iscut[i])
printf("%d ", i);
return 0;
}
割边
定义:
类似割点,在一个联通分量中,如果删除一个边,把这个联通分量分成了两个,这个边称为割边。
寻找:判断割点的方法——DFS求割点的算法
有意思的是:只要将
low[v]>=num[u]
改为low[v]>num[u]
,那么边(u,v)
就是一条割边。
这表示u的支路v以及v的后代只能回退到v,到不了u,那么边(u,v)肯定是割边