求割点:
割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点
若low[v]>=pre[u],则u为割点。
这说明v的子孙不能够通过其他边到达u的祖先,这样去掉u之后,图必然分裂为两个子图。这样我们处理点u时,首先递归u的子节点v,然后从v回溯至u后,如果发现上述不等式成立,则找到了一个割点u,并且u和v的子树构成一个块。
求割边(桥)
割边(桥):删掉它之后,图分裂为两个或两个以上的子图
若low[v]>pre[u],则(u,v)为割边。
但是实际处理时我们并不这样判断,因为有的图上可能有重边,这样不好处理。我们记录每条边的标号(一条无向边拆成的两条有向边标号相同),记录每个点的父亲到它的边的标号,如果边(u,v)是v的父亲边,就不能用pre[u]更新low[v]。这样如果遍历完v的所有子节点后,发现low[v]=pre[v],说明u的父亲边(u,v)为割边。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define maxn 1000
int pre[maxn], iscut[maxn], low[maxn], dfs_clock;
//iscut[i]判断某个点是否为割点
vector<int>G[maxn];
int dfs(int u, int fa)
{
int lowu=pre[u]=++dfs_clock;
int child=0;
for(int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
if(!pre[v])
{
child++;
int lowv=dfs(v,u);
lowu=min(lowu,lowv);
if(lowv>=pre[u])iscut[u]=true;//u不为树根,必须满足lowv>=preu
//if(lowv>pre[u])则(u, v)满足桥
}else if(pre[v]<pre[u]&&v!=fa)
lowu=min(lowu, pre[v]);
}
if(fa<0&&child==1)iscut[u]=0;//如果u为树根,那么u必须有多于1棵子树,才能为割点
low[u]=lowu;
return lowu;
}
void find_cutpoint(int n)
{
memset(pre,0,sizeof(pre));
memset(low,0,sizeof(low));
memset(iscut,0,sizeof(iscut));
dfs_clock=0;
for(int i = 1; i <= n; i++)if(!pre[i])dfs(i,-1);//点从1开始
}
int main()
{
int n;
while(~scanf("%d",&n)&&n)
{
for(int i = 1; i <= n; i++)G[i].clear();
int tmp;
while(scanf("%d",&tmp)&&tmp)
{
while(getchar()!='\n')
{
int t;
scanf("%d",&t);
G[tmp].push_back(t);
G[t].push_back(tmp);
}
}
find_cutpoint(n);
int ans = 0;
for(int i = 1; i <= n; i++)if(iscut[i])ans++;
printf("%d\n",ans);
}
return 0;
}