双连通分量
前置知识
基本概念
双连通分量又分点双连通分量和边双连通分量两种。若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。一个无向图中的每一个极大点(边)双连通子图称作此无向图的点(边)双连通分量。求双连通分量可用Tarjan算法。——百度
简单来说,一个没有割点(桥)的无向图就是点(边)双连通分量。
以下图为例:
对于这个图来说一共有2个点双连通分量,分别是:
(1,2,5),(2,3,4)
有3个边双连通分量,分别是:
(1,2,5),(2,3,4),(1,2,3,4,5)
点双连通分量
首先对于一个点双连通分量来说,里面一定没有割点。
所以说当我们查找到一个割点u的时候,就将以u为根的搜索子树内 还不属于任何一个点双连通分量 且 不为割点 或 为割点 但 与那些以u为根的搜索子树内 还不属于任何一个点双连通分量 且 不为割点 的点 直接连通的点 为一个点双连通分量。
简单来说,就是当我们查找到一个割点的时候,我们像强连通分量那样让仍在栈内的点成为一个点双连通分量。
最后附上代码:
代码
void tarjan(int u,int fa) {
dfn[u]=low[u]=++index;
s.push(u);
for(int i=0; i<g[u].size(); i++) {
int v=g[u][i];
if(v==fa)continue;
if(!dfn[v]) {
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
cnt++;
int d;
do{
d=s.top();
s.pop();
gcc[d]=cnt;
}while(d!=v);//将目前在栈中的点都归于当前的点双连通分量中去
gcc[u]=cnt;//将该点也归于当前的点双连通分量中去
}
} else {
low[u]=min(low[u],dfn[v]);
}
}
}
完美完结
边双连通分量
我们只需要将这个图中所有的桥都求出来,再将它们删除,此时图中每一个连通块都是一个边双连通分量
代码
void tarjan(int u,int fa) {
dfn[u]=low[u]=++index;
for(int i=head[u]; i!=-1; i=a[i].next) {//在这里,由于我们要将桥都记录下来,所以选择使用前向星
int v=a[i].to;
if(v==fa)continue;
if(!dfn[v]) {
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])vis[i]=true;//这条边为桥
} else {
low[u]=min(low[u],dfn[v]);
}
}
}
void dfs(int u) {//遍历删去桥的图
ecc[u]=cnt;//记录
for(int i=head[u]; i!=-1; i=a[i].next) {
int v=a[i].to;
if(!ecc[v]&&!vis[i])dfs(v);
}
}
例题
P3225 矿场搭建
这道题可以用割点过,但是思路稍显复杂。
用点双连通分量思路会清晰很多。
详细可以看这篇题解
hdu4612 Warm up
Problem Description
N planets are connected by M bidirectional channels that allow instant transportation. It's always possible to travel between any two planets through these channels.
If we can isolate some planets from others by breaking only one channel , the channel is called a bridge of the transportation system.
People don't like to be isolated. So they ask what's the minimal number of bridges they can have if they decide to build a new channel.
Note that there could be more than one channel between two planets.
Input
The input contains multiple cases.
Each case starts with two positive integers N ((2 leq Nleq2 imes10^5)) and M ((1 leq Mleq10^6)) , indicating the number of planets and the number of channels.
Next M lines each contains two positive integers A and B, indicating a channel between planet A and B in the system. Planets are numbered by 1 to N.
A line with two integers '0' terminates the input.
Output
For each case, output the minimal number of bridges after building a new channel in a line.
Sample Input
(4) (4)
(1) (2)
(1) (3)
(1) (4)
(2) (3)
(0) (0)
Sample Output
(0)
题目大意
对于一个联通的无向图,问添加一条边后至少剩下几个桥
解题思路
先用边双连通分量缩点,树的直径即为最多减少的桥数
代码
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
struct ed {
int to,next;
} a[2000005];
int t,head[200005];
int n,m,cnt;
int dfn[200005],low[200005],scc[200005],in;
vector<int>g[200005];
bool cutedge[2000005];
void tarjan(int u,int fa) {//tarjan模板
dfn[u]=low[u]=++in;
for(int i=head[u]; i!=-1; i=a[i].next) {
if((i^1)==fa)continue;
int v=a[i].to;
if(!dfn[v]) {
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])cutedge[i]=cutedge[i^1]=true;
} else {
low[u]=min(low[u],dfn[v]);
}
}
}
void dfs(int u) {//遍历
scc[u]=cnt;
for(int i=head[u]; i!=-1; i=a[i].next) {
int v=a[i].to;
if(!scc[v]&&!cutedge[i])dfs(v);
}
}
void add(int u,int v) {//前向星加边
a[t].to=v;
a[t].next=head[u];
head[u]=t++;
}
void clean() {//初始化
for(int i=0;i<=200000;i++)g[i].clear();
memset(cutedge,false,sizeof(cutedge));
memset(head,-1,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(scc,0,sizeof(scc));
memset(d1,0,sizeof(scc));
memset(d2,0,sizeof(scc));
memset(a,0,sizeof(a));
ans=0,cnt=0,in=0,t=0;
}
int ans,d1[200005],d2[200005];
int dia(int u,int s,int fa) {//树的直径
d1[u]=s;
for(int i=0; i<g[u].size(); i++) {
int v=g[u][i];
if(v==fa)continue;
int l=dia(v,s+1,u)+1;
if(l>=d1[u])d2[u]=d1[u],d1[u]=l;
else if(l>=d2[u])d2[u]=l;
}
return d1[u];
}
int main() {
while(cin>>n>>m) {
if(n==0&&m==0)break;
clean();
while(m--) {
int u,v;
cin>>u>>v;
add(u,v),add(v,u);
}
for(int i=1; i<=n; i++) {
if(!dfn[i])tarjan(i,-1);//求桥
}
for(int i=1; i<=n; i++) {
if(!scc[i]){
cnt++;
dfs(i);//求边双连通分量
}
}
for(int u=1; u<=n; u++) {//缩点
for(int i=head[u]; i!=-1; i=a[i].next) {
int v=a[i].to;
if(scc[u]!=scc[v])g[scc[u]].push_back(scc[v]);
}
}
dia(1,0,-1);//在缩完点后的树上跑树的直径
for(int i=1; i<=cnt; i++)ans=max(ans,d1[i]+d2[i]);
cout<<cnt-1-ans<<endl;//总边数-树的直径=至少剩下几个桥
}
return 0;
}