题目链接:http://poj.org/problem?id=1144
割点与割边的数量我们可以通过tarjan的思想从一个点开始对其余点进行访问。访问的顺序构成一棵dfs树,其中根节点到任何一个结点都只有唯一的一条路径。算法基于以下两个定理:
定理一:
dfs树的根结点T是割点当且仅当他有两个或者更多的子节点。因为dfs树上任何点的子树都是不连通的,否则就会构成环,与dfs树的定义矛盾。故定理得证。
定理二 :
dfs树上的非根结点是割点当且仅当u至少存在一个子节点v,v的所有后代都没有回退边连回u的祖先,也就是从v出发能够访问到的最浅的结点比u的深度大。因为此时把u割去之后一定会使得v为根的分支被割离。
根据以上定理,我们只要记录dfs的顺序,每个点的开始访问的最浅dfs深度并进行比较,即low[v]>=low[u]就可以得到割点的数量。对于割边,我们只要u的子结点v有low[v]>num[u]就说明(u,v)是割边。
代码如下:
1 #include<cstdio> 2 #include<vector> 3 #include<string.h> 4 using namespace std; 5 const int maxn=109; 6 int low[maxn],num[maxn];//分别保存dfs树上结点能到达的最浅深度和dfs的访问顺序 7 bool iscut[maxn];//记录是否是割点 8 vector<int>G[maxn];//存边 9 int dfn;//记录递归的顺序,用于给num赋值 10 int ans=0;//ans记录割点的数量 11 int n; 12 void tarjan(int u,int fa)//参数分别是当前搜索的结点以及其父结点 13 { 14 low[u]=num[u]=++dfn;//设置dfs的访问顺序,u是dfs访问的第一个点 15 int child=0;//u的子树的数量 16 for(int i=0;i<G[u].size();i++) 17 { 18 int v=G[u][i]; 19 if(!num[v])//v点没有进入过dfs树,也就是没有访问过 20 { 21 child++; 22 tarjan(v,u); 23 low[u]=min(low[u],low[v]);//low[i]的意义是点i所能到的dfs深度最浅的值,father结点由child结点来更新 24 if(low[v]>=num[u]&&u!=1)//注意要判断u不是根节点,因为定理中是分为非根节点以及根节点进行讨论的,根节点根节点最后讨论 25 { 26 iscut[u]=1; 27 } 28 } 29 // else low[u]=min(low[u],num[v]); 30 else if(num[v]<num[u]&&v!=fa)// fa也是u的邻居,在之前已经访问过; 31 //处理回退边 ,u的子节点可以不通过u访问u以上的结点 32 { 33 low[u]=min(low[u],num[v]); 34 } 35 } 36 if(u==1&&child>=2) 37 { 38 iscut[u]=1;//根结点有两个或以上的独立子树 39 } 40 } 41 int main() 42 { 43 while(scanf("%d",&n)&&n) 44 { 45 int t,k; 46 for(int i=1;i<=n;i++)G[i].clear(); 47 memset(iscut,0,sizeof(iscut)); 48 memset(low,0,sizeof(low)); 49 memset(num,0,sizeof(num)); 50 ans=0; 51 dfn=0; 52 while(scanf("%d",&t)==1&&t) 53 { 54 while(getchar()!=' ') 55 { 56 scanf("%d",&k); 57 G[t].push_back(k); 58 G[k].push_back(t); 59 } 60 } 61 tarjan(1,-1); 62 for(int i=1;i<=n;i++)ans+=iscut[i];//扫描割点的数量 63 64 printf("%d ",ans); 65 } 66 }