无向图的双连通分量:eg1:acwing 395.冗余路径(e-dcc);eg2:acwing 1183.电力(v-dcc);
无向图的双连通分量:
一些概念:
桥(也叫割边):
割点:
①:边连通分量(e-dcc):
极大的不含有桥的连通块被称为边双连通分量。
图中的两个边双连通分量之间一定有桥
每个桥至少属于两个边双连通分量
②:点连通分量(v-dcc):
极大的不包含割点的连通块被称为点双连通分量。
每个割点至少属于两个点双连通分量。
图中的两个点双连通分量之间一定有割点
e-dcc算法实现:
和求有向图的强连通分量类似,引入时间戳的概念;
如何找到桥:
设桥的两个端点为x,y则满足dfn[x]<low[y];
如何找到e-dcc:
①:将所有的桥边删去,剩下的连通区域都是e-dcc;
②:和有向图gcc类似,用栈来做;
eg1:冗余路径;
题意其实就是:给一个无向连通图,问最少加几条边会变成边双连通分量;
先求e-dcc,然后缩点,因为在连通分量内部加边是无意义的,所有缩点,缩完点后,很明显原来的图就变成了一棵树,要使该树变成边双连通分量,所以度数为1的点(叶子节点)都要加边,最少是要加叶子节点个数/2(上取整)个;
code:
//#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rep1(i,a,n) for( int i=a;i<n;++i)
#define rep2(i,a,n) for( int i=a;i<=n;++i)
#define per1(i,n,a) for( int i=n;i>a;i--)
#define per2(i,n,a) for( int i=n;i>=a;i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define memset(a,i,b) memset((a),(i),sizeof (b))
#define memcpy(a,i,b) memcpy((a),(i),sizeof (b))
#define pro_q priority_queue
#define pb push_back
#define pf push_front
#define endl "\n"
#define lowbit(m) ((-m)&(m))
#define YES cout<<"YES\n"
#define NO cout<<"NO\n"
#define Yes cout<<"Yes\n"
#define No cout<<"No\n"
#define yes cout<<"yes\n"
#define no cout<<"no\n"
#define yi first
#define er second
using namespace std;
typedef pair<int,int> PII;
typedef pair<long long,long long>PLL;
typedef pair<int,PII> PIII;
typedef long long ll;
typedef double dob;
const int N=5e3+10,M=2e4+10;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int stk[N],dcc_cnt,top;
int is_birdge[M],id[N],d[N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u,int from)
{
dfn[u]=low[u]=++timestamp;
stk[++top]=u;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(!dfn[j])
{
tarjan(j,i);
low[u]=min(low[u],low[j]);
if(dfn[u]<low[j])
is_birdge[i]=is_birdge[i^1]=1;
}
else if(i!=(from^1))low[u]=min(low[u],dfn[j]);
}
if(dfn[u]==low[u])
{
++dcc_cnt;
int y;
do
{
y=stk[top--];
id[y]=dcc_cnt;
}while(y!=u);
}
}
signed main()
{
quick_cin();
cin>>n>>m;
memset(h,-1,h);
while(m--)
{
int a,b;cin>>a>>b;
add(a,b),add(b,a);
}
tarjan(1,-1);
rep1(i,0,idx)
if(is_birdge[i])d[id[e[i]]]++;
int cnt=0;
rep2(i,1,dcc_cnt)
if(d[i]==1)cnt++;
cout<<(cnt+1)/2;
return 0;
}
eg2:电力;
v-dcc算法实现;
如何判断割点:
要求low[y] > dfn[x] y能到达的最早的节点>x的时间戳
否则low[y] ≤ dfn[x]时,即y能到比xdfs序小的点,则存在环
low[y]≤dfn[x]的情况下
1 如果x不是根节点 那么x是割点
2 x是根节点 至少有两个子节点yi
且有low[yi]≥dfn[x]
如果只有一个子结点 去完根节点x
x
| → y1 子节点部分还是连通的
y1
如果有两个子结点 去完根节点x
x
/ \ → y1 y2 之间不连通
y1 y2
//#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rep1(i,a,n) for( int i=a;i<n;++i)
#define rep2(i,a,n) for( int i=a;i<=n;++i)
#define per1(i,n,a) for( int i=n;i>a;i--)
#define per2(i,n,a) for( int i=n;i>=a;i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define memset(a,i,b) memset((a),(i),sizeof (b))
#define memcpy(a,i,b) memcpy((a),(i),sizeof (b))
#define pro_q priority_queue
#define pb push_back
#define pf push_front
#define endl "\n"
#define lowbit(m) ((-m)&(m))
#define YES cout<<"YES\n"
#define NO cout<<"NO\n"
#define Yes cout<<"Yes\n"
#define No cout<<"No\n"
#define yes cout<<"yes\n"
#define no cout<<"no\n"
#define yi first
#define er second
using namespace std;
typedef pair<int,int> PII;
typedef pair<long long,long long>PLL;
typedef pair<int,PII> PIII;
typedef long long ll;
typedef double dob;
const int N=1e4+10,M=3e4+10;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int root,ans;
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)
{
dfn[u]=low[u]=++timestamp;
int cnt=0;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(!dfn[j])
{
tarjan(j);
low[u]=min(low[u],low[j]);
if(low[j]>=dfn[u])cnt++;
}
else low[u]=min(low[u],dfn[j]);
}
if(u!=root)cnt++;
ans=max(ans,cnt);
}
signed main()
{
quick_cin();
while(cin>>n>>m,n||m)
{
memset(dfn,0,dfn);
memset(h,-1,h);
idx=timestamp=0;
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b),add(b,a);
}
ans=0;
int cnt=0;
for(root=0;root<n;++root)
{
if(!dfn[root])
{
cnt++;
tarjan(root);
}
}
cout<<ans+cnt-1<<endl;
}
return 0;
}