题目大意:有一个电话公司,他们的电话可以直连,也可以通过电话交换机与其他电话相连。当供电不足时,有的电话或者电话交换机就不能用了,会导致公司里剩余的电话不能相互通话,我们把这样的电话或者电话交换机称为关节点。
分析:抽象出来的模型就是,一个无向连通图求割点。
无向连通图求割点的Tarjan算法,需要下面3个数组。
dfn[i]表示访问顺序,也称开始时间。
low[i]表示i或者i的子树中能够通过非父子边(父子边就是搜索树上的边)追溯到的最早的节点的DFS开始时间。
fa[i]记录i的父节点。
判断一个顶点是否为割点,只需满足下面任意一个条件即可:
(1) u为树根,且u有多于一个子树。
(2) u不为树根,且存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得dfn(u)<=low(v)。
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 111;
int n;
vector<int> g[maxn];
int dfn[maxn], low[maxn];
bool cut[maxn]; //点v是否为割点
int fa[maxn];
int index; //DFS时间戳
void tarjan(int u, int father) {
dfn[u] = low[u] = ++index;
fa[u] = father;
int len = g[u].size();
for(int i = 0; i < len; i++) {
int v = g[u][i];
if(dfn[v] == 0) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
}
else if(v != father)
low[u] = min(low[u], dfn[v]);
}
}
int main() {
while(scanf("%d", &n) && n) {
for(int i = 1; i <= n; i++)
g[i].clear();
int u, u0;
while(scanf("%d", &u) && u) {
u0 = u;
int v;
while(getchar() != '\n') {
scanf("%d", &v);
g[u].push_back(v);
g[v].push_back(u);
}
}
memset(dfn, 0, sizeof(dfn));
memset(cut, 0, sizeof(cut));
memset(fa, 0, sizeof(fa));
memset(low, 0, sizeof(low));
int son = 0;
index = 0;
tarjan(u0, 0);
for(int i = 1; i <= n; i++) {
if(i == u0) continue;
int v = fa[i];
if(v == u0) son++; //记录根的子树的个数,即割点条件(1)
else if(dfn[v] <= low[i]) cut[v] = 1; //割点条件(2)
}
if(son > 1) cut[u0] = 1;
int ans = 0;
for(int i = 1; i <= n; i++)
if(cut[i]) ans++;
printf("%d\n", ans);
}
return 0;
}