点连通度与边连通度
在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。一个图的点连通度的定义为,最小割点集合中的顶点数。
类似的,如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。一个图的边连通度的定义为,最小割边集合中的边数。
双连通图、割点与桥
如果一个无向连通图的点连通度大于1,则称该图是点双连通的(point biconnected),简称双连通或重连通。一个图有割点,当且仅当这个图的点连通度为1,则割点集合的唯一元素被称为割点(cut point),又叫关节点(articulation point)。
如果一个无向连通图的边连通度大于1,则称该图是边双连通的(edge biconnected),简称双连通或重连通。一个图有桥,当且仅当这个图的边连通度为1,则割边集合的唯一元素被称为桥(bridge),又叫关节边(articulation edge)。
可以看出,点双连通与边双连通都可以简称为双连通,它们之间是有着某种联系的,下文中提到的双连通,均既可指点双连通,又可指边双连通。
求割点和桥
/*
* vis[v] 记录的是节点v当前的访问状态:1表示在栈中,0表示未访问,2表示已经访问
* dfn[v] 记录的是节点v被访问时的深度
* low[v] 记录的是点v可以到达的,访问时间最早的祖先
* 在深度遍历图的过程中,记录下每个节点的深度。
* 对当前节点cur,以及和它相连的点i,有两种情况
* (1)i没有被访问过,这时递归访问节点,并用i的可以到达的最早的祖先来更新cur的low值
* (2)i当前在栈中,这时说明图中有一个环,用i的深度更新cur的low值
*
* cur是割点的条件:cur是根,且有大于一个儿子,或者cur不是根,且cur有一个儿子使得low[v]>dfn[cur]
* (cur,i)是桥的条件是Low[i]>dfn[cur]
*
* 复杂度:O(|E|+|V|)
*
* 输入:cur 当前节点
* father 当前节点的父节点
* dep 当前节点被访问时的深度
* n 图的总点数
* edge 图的邻接矩阵(从0开始编号)
*
* 输出:bridge 全局变量,bridge[u][v]表示边(u,v)是否是一个桥
* cut 全局变量 cut[v]表示节点v是否是一个割点
*/
const int MAXN = 1003;
int edge[MAXN][MAXN];
int bridge[MAXN][MAXN], cut[MAXN];
int low[MAXN], dfn[MAXN], vis[MAXN];
int ans;
void cut_bridge(int cur, int father, int dep, int n) {
vis[cur] = 1;
dfn[cur] = low[cur] = dep;
int children = 0;
for (int i = 0; i < n; i++) {
if (edge[cur][i]) {
if (i != father && 1 == vis[i]) {
if (dfn[i] < low[cur]) {
low[cur] = dfn[i];
}
} else if (0 == vis[i]) {
cut_bridge(i, cur, dep+1, n);
children++;
if (low[i] < low[cur]) {
low[cur] = low[i];
}
if ((father == -1 && children > 1) || (father != -1 && low[i] >= dfn[cur])) {
cut[cur] = true;
}
if (low[i] > dfn[cur]) {
bridge[cur][i] = bridge[i][cur] = true;
}
}
}
}
vis[cur] = 2;
}
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <string>
#include <algorithm>
#define ll long long
using namespace std;
const int inf=0x3ffffff;
const int MAXN = 103;
const double eps = 1e-6;
int edge[MAXN][MAXN];
int bridge[MAXN][MAXN], cut[MAXN];
int low[MAXN], dfn[MAXN], vis[MAXN];
int ans;
void cut_bridge(int cur, int father, int dep, int n) {
vis[cur] = 1;
dfn[cur] = low[cur] = dep;
int children = 0;
for (int i = 0; i < n; i++) {
if (edge[cur][i]) {
if (i != father && 1 == vis[i]) {
if (dfn[i] < low[cur]) {
low[cur] = dfn[i];
}
} else if (0 == vis[i]) {
cut_bridge(i, cur, dep+1, n);
children++;
if (low[i] < low[cur]) {
low[cur] = low[i];
}
if ((father == -1 && children > 1) || (father != -1 && low[i] >= dfn[cur])) {
ans += cut[cur] != true;
cut[cur] = true;
}
if (low[i] > dfn[cur]) {
bridge[cur][i] = bridge[i][cur] = true;
}
}
}
}
vis[cur] = 2;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("1.txt", "r", stdin);
#endif
int n, i, j, v, p, len;
char str[1000];
while(cin >> n, n) {
memset(edge, 0, sizeof(edge));
while(cin >> v, v) {
v--;
gets(str);
len = strlen(str);
int value = 0;
for (i = 0; i < len; i++) {
if (str[i] == ' ') {
if (value == 0) {
continue;
}
edge[v][value-1] = edge[value-1][v] = 1;
value = 0;
} else {
value = value * 10 + str[i] - '0';
}
}
if (value) {
edge[v][value-1] = edge[value-1][v] = 1;
value = 0;
}
}
ans = 0;
memset(vis, 0, sizeof(vis));
memset(cut, 0, sizeof(cut));
memset(bridge, 0, sizeof(bridge));
for (i = 0; i < n; i++) {
if (!vis[i]) {
cut_bridge(i, -1, 0, n);
}
}
cout << ans << endl;
}
return 0;
}