题目大意:有一个电话公司,他们的电话可以直连,也可以通过电话交换机与其他电话相连。当供电不足时,有的电话或者电话交换机就不能用了,会导致公司里剩余的电话不能相互通话,我们把这样的电话或者电话交换机称为关节点。
分析:抽象出来的模型就是,一个无向连通图求割点。
无向连通图求割点的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; }