[点割集与割点]
设无向图G=(V,E),G的连通分支数为X。
如果说存在顶点子集V’,删除V’中所有点(以及点关联的边)之后,子图V-V’的连通分支数Y>X。
那么V’是G的割点集。
如果割点集只有一个节点,则称为割点。
特别的:
无向连通图中G中,去掉某一节点和该节点所有有关联的边之后,剩下的图如果不再连通(连通分支数增加),称该点为割点。<也称为关节点:Articulation Point>。
[点连通度]
图G的点连通度定义为最小割点集合中的顶点数目。
[Tarjan算法求割点]
设在无向图G的DFS树中:
对于一个候选节点v1,当它满足下列两种情况的一种的时候,判断v是割点。[需要注意的是:这里的节点是指在深搜树上的节点而非原图上的节点,相当于将一个无向连通图转换为一棵深搜树后进行相应的判断操作]
1.如果节点v是根节点 v1 == root且root有两个以上的孩子,那么根节点root必定是割点。
2.如果节点v1不是根节点。
v1是G的割点当且仅当v1存在一个子节点v2使得v2及其后代都没有反向边回到v1的祖先。(如果v2及v2的后代有反向边连回到v1不影响该结论).
证明:
1.考虑v1的任意子节点v2。如果v2及其后代不能通过后向边连回father节点,那么删除v1后,v2和father不再连通。
[这里体现在代码上即:low[v2] >= dfn[v1]]
如果不理解这两个变量的含义,传送门
2.反过来,如果v2或者它的某一个后代存在一条反向边连回father节点,则删除v1后,以v2为根的整棵子树都可以连回father。
/*
POJ 1144
算法:裸求无向连通图的割点
错误:误以为和一个节点v1有关系的节点{v2,v3....}在深搜树上一定是v1的儿子,实际上各种情况都存在(而且很有可能是叶子)
因为{v2,v3...}也会与其他的节点有关系
Accepted
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>
using namespace std;
const int maxn = 105;
vector<int> edge[maxn];
int parent[maxn];//记录父节点,本题无用
int dfn[maxn],low[maxn];
int belong[maxn];//本题无用
bool instack[maxn];//标记是否在栈中,实际上就是是否访问过
bool cutpoint[maxn];//cutpoint[x] = true表示标号为x的节点是割点
int time,color;
int Min(int x,int y) { return (x < y ) ? x : y ; }
void Init()
{
time = 0;
color = 0;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(parent,0,sizeof(parent));
memset(belong,0,sizeof(belong));
memset(instack,false,sizeof(instack));
memset(cutpoint,false,sizeof(cutpoint));
for(int i = 0 ; i < maxn ; ++i)
edge[i].clear();
}
void Tarjan(int v1)
{
int v2;
int childnum = 0;//孩子数目
dfn[v1] = low[v1] = ++time;
instack[v1] = true;
for(unsigned i = 0 ; i < edge[v1].size() ; ++i){
v2 = edge[v1][i];
childnum++;
if(dfn[v2] == 0){//继续搜索
Tarjan(v2);
low[v1] = Min(low[v1],low[v2]);
/*if(v1 == 1 && edge[v1].size() > 1)//这样只是和v1节点有连边关系未必是孩子数
cutpoint[v1] = true;
例如v1 = 1 ,edge[v1][0] = 2,edge[v1][1] = 3....虽然2,3是v1的后继,但是在深搜树上,2,3可能是最后的叶子
反而不是v1的儿子*/
if(v1 == 1 && childnum >= 2)//case1,root 节点
cutpoint[v1] = true;
if(v1 != 1 && low[v2] >= dfn[v1])//case2,非根节点
cutpoint[v1] = true;
}
else if(instack[v2] == true)//后向边
low[v1] = Min(low[v1],dfn[v2]);
//后向边的意义:如果(v1,v2)是一条后向边,说明v1可以通过v2回到v1的祖先
}
}
int main()
{
int v1,v2,n;
char c;
while(scanf("%d",&n) != EOF && n){
Init();
while(scanf("%d",&v1) && v1){
while( (c = getchar() ) != '\n'){
scanf("%d",&v2);
edge[v1].push_back(v2);
edge[v2].push_back(v1);//无向图
}
}
Tarjan(1);//默认1节点是根节点
int ans = 0;
for(int i = 1 ; i <= n ; ++i)
if(cutpoint[i])
ans++;
printf("%d\n",ans);
//printf("ans : %d\n",ans);
}
return 0;
}