- 割点:删去一点
u
及其所连的边后,原来连通的图形成多个连通块,这个点u
即为割点。 - 搜索树:对图进行深度优先搜索按搜索顺序及节点之间的父子关系产生的树状图。
dfn[cur]
:记录节点cur
的访问时间,以1开始。low[cur]
:记录节点cur
能够通过后向边(返祖边,而且不能是父子边)到达的遍历顺序最早(dfn
最小)的祖先的dfn
值。- 父子边e<u,v>:当u是v在搜索树中的父亲时,e<u,v>称为父子边。
割点u须满足以下两点之一:
u
是树根且有多余一个子树;- 存在
v
属于u
的孩子,low[v]>=dfn[u]
。
如何理解判定 2:
当low[v]>=dfn[u]
时,在不通过节点 u 的限制下,u 的子节点 v 不能经过其它节点到达 u 的祖先。所以当把 u 删去之后,它的子树独立,即它的子孙节点构成的图是独立的连通分支。
void tarjan_Gpoint(int cur, int father){
dfn[cur]=low[cur] = ++idx; // idx为计数器,深搜访问的次序
for(int i = head[cur]; i != -1; i = edge[i].pre){
int to = edge[i].cur;
if(dfn[to] == 0){
tarjan_Gpoint(to, cur);
low[cur] = min(low[to], low[cur]);
if(low[to] >= dfn[cur]){
if(cur == father)
root ++; // 根的子树个数
else
Gpoint[cur] ++; // 标记cur为割点
}
} else {
low[cur] = min(dfn[to], low[cur]);
}
}
}
int solve_Gpoint(){
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(Gpoint, 0, sizeof(Gpoint));
idx = root = 0;
tarjan_Gpoint(1, 1);
int cnt = 0; // 割点个数
if(root > 1) // 判定 1
cnt ++;
for(int i = 2; i <= n; i ++)
if(Gpoint[i]) cnt ++;
return cnt;
}
- 割边:删去一条边后,原来连通的图形成多个连通块,这条边
e
即为割边。
割边 e<u,v> 须满足:
- 存在
v
属于u
的孩子,low[v]>dfn[u]
。
如何理解判定:
当low[v]>dfn[u]
时,在不通过节点 u 的限制下,u 的子节点 v 不能经过其它节点到达 u,即节点 u,v 之间不存在环路。所以当把 e 删去之后,它的两边构成独立的连通分支。
priority_queue<Edge> cutEdges; // 所有的割边
void tarjan_cutEdges(int cur, int father){
dfn[cur]=low[cur] = ++idx; // idx为计数器,深搜访问的次序
for(int i = head[cur]; i != -1; i = edge[i].pre){
int to = edge[i].cur;
if(cur == father) continue;
if(dfn[to] == 0){
tarjan_cutEdges(to, cur);
low[cur] = min(low[to], low[cur]);
if(low[to] > dfn[cur]) {
cutEdges.push(edge[i]); // 标记 edge[i] 为割边
}
} else {
low[cur] = min(dfn[to], low[cur]);
}
}
}
void solve_cutEdges(){
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
idx = 0;
tarjan_cutEdges(1, 1);
return;
}
- 请在 ZJGSU OJ 上编程解决以下题目: