一.割点和桥
无向连通图中,如果删除某点后,图变
成不连通,则称该点为割点。
无向连通图中,如果删除某边后,图变
成不连通,则称该边为桥。
所以说啊,割点和桥这个概念的应该范围应该只是在无向连通图中的!这一点要十分注意!
二.怎样判定
dfn[u]定义和前面类似,但是low[u]定义为u
或者u的子树中能够通过非父子边(父子边
就是搜索树上的边)追溯到的最早的节点
的DFS开始时间
一个顶点u是割点,当且仅当满足(1)或(2)
(1) u为树根,且u有多于一个子树。
(2) u不为树根,且存在(u,v)为树枝边(或称父子
边,即u为v在搜索树中的父亲),使得
dfn(u)<=low(v)。
一条边(u,v)是桥,当且仅当(u,v)为树枝边,且
满足dfn(u)<low(v)(前提是其没有重边)
然后我们只需要根据这个思路去写程序就好啦!
三.思路解析
根据上面的两条定理我们知道我们只要知道每个点的dfn和low那么我们不难判定这个点是否是割点,那么下一步当然就是根据low,dfn的定义去求这连个状态就好啦,那么当然为了求low我们自然要求dfs了,这里和原来的tarjan有所不同的是我们缺少了vis这个状态也没有栈这个结构,那么是为什么呢,
#include <iostream>
#include <cstring>
#include <cstdio>
#include <stack>
#include <vector>
using namespace std;
const int maxn=100000;
int cv[maxn],n,m,fa[maxn],index,dfn[maxn],low[maxn];
stack <int> st;
vector <int> map[maxn];
int min(int a,int b)
{
if(a<b) return a;
else return b;
}
void tarjan(int u,int father)
{
index++;
dfn[u]=low[u]=index;
fa[u]=father;
for(int i=0;i<map[u].size();i++)
{
int v=map[u][i];
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
}
else if(v!=father)
low[u]=min(low[u],dfn[v]);
}
}
void count()
{
int rootson=0;
for(int i=2;i<=n;i++)
{
int v=fa[i];
if(v==1) rootson++;
else if(low[i]>=dfn[v]) cv[v]=1;
}
if(rootson>1) cv[1]=1;
for(int i=1;i<=n;i++)
if(cv[i]) printf("%d\n",i);
for(int i=2;i<=n;i++)
{
int v=fa[i];
if(dfn[v]<low[i])
{
printf("%d,%d\n",v,i);
}
}
}
void init()
{
index=0;
while(!st.empty()) st.pop();
memset(cv,0,sizeof(cv));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(fa,0,sizeof(fa));
for(int i=0;i<maxn;i++) map[i].clear();
}
int main()
{
freopen("input.txt","r",stdin);
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
map[u].push_back(v);
map[v].push_back(u);
}
tarjan(1,0);
count();
}
return 0;
}