无向图的双连通分量
边双连通分量
指极大的不存在桥的联通分量.去掉桥会使图不连通.
tarjan算法,时间复杂度O(n+m)
1.以未建立时间戳的点为起点进行dfs,将该点压入栈中,建立该点的时间戳(f数组)并初始化从该点出发的最小的时间戳(mf数组).
2.遍历以该点为起点的所有终点,若未建立时间戳则继续进行dfs,结束后更新mf数组;否则若不为来时的边上的点,也更新mf数组.如果终点的mf值大于该点的f值,说明这条边是桥.
3.递归回到该点后,该点的mf值如果等于f值表示它是一个双连通分量的最高点,此时不断出栈至该点即可得到这个双连通分量的所有点集.
4.若要转换为树,遍历所有边,如果不在同一个双连通分量中,在这两个双连通分量之间连边.
最少加(cnt+1)/2向下取整条边把无向图中的所有点转换成双连通分量中的点,cnt为缩点后的度为1的点的个数
#include<iostream>
#include<cstdio>
#include<stack>
#include<cstring>
using namespace std;
const int N=5e3+10,M=2e4+10;
int h[N],nex[M],to[M],con=0,ts=0,dcc=0,id[N],deg[N],c[M];
int f[N],mf[N],in_stack[N];
void add(int a,int b)
{
nex[con]=h[a];
h[a]=con;
to[con]=b;
c[con++]=a;
}
stack<int>s;
void dfs(int x,int from)
{
// cout<<x<<endl;
f[x]=mf[x]=++ts;
s.push(x);
in_stack[x]=1;
for(int i=h[x];i>=0;i=nex[i])
{
int t=to[i];
if(!f[t])
{
dfs(t,i);
mf[x]=min(mf[x],mf[t]);
// cout<<x<<' '<<f[x]<<' '<<t<<' '<<mf[t]<<endl;
if(mf[t]>f[x])
{这条边是桥;}
}
else if(i!=(from^1))
mf[x]=min(mf[x],mf[t]);
}
if(f[x]==mf[x])
{
int t;
do
{
t=s.top();s.pop();
in_stack[t]=0;
id[t]=dcc;
}while(t!=x);
dcc++;
}
}
int main()
{
int f,r;cin>>f>>r;
memset(h,-1,sizeof(h));
for(int i=0;i<r;++i)
{
int a,b;scanf("%d%d",&a,&b);
add(a,b);add(b,a);
}
dfs(1,-1);
int ans=0;
for(int i=0;i<con;i+=2)
{
if(id[c[i]]!=id[to[i]])
{
deg[id[c[i]]]++,deg[id[to[i]]]++;
}
}
for(int i=0;i<dcc;++i)
{
if(deg[i]==1)
ans++;
}
cout<<(ans+1)/2<<endl;
return 0;
}