无向图的双连通分量:eg1:acwing 395.冗余路径(e-dcc);eg2:acwing 1183.电力(v-dcc);eg3:acwing 396.矿场搭建;
无向图的双连通分量:
一些概念:
桥(也叫割边):
割点:
①:边连通分量(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;
}
eg3:矿场搭建;
题意:给一个不一定连通的无向图,问最少在几个点设置出口,使得任意一个出口坏掉后,其他所有点都可以和某个出口连通 ;
结论:
1 无割点 放2个出口 方案数 *= C[cnt][2] = cnt*(cnt-1)/2
2 有割点 V-DCC==1 放1个出口 方案数 *= C[cnt-1][1] = cnt-1 (不包含割点)
3 有割点 V-DCC>=2 放0个出口 方案数 *= 1
//#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 unsigned long long ULL;
typedef pair<long long,long long>PLL;
typedef pair<int,PII> PIII;
typedef long long LL;
typedef double dob;
const int N=1e3+10,M=1e3+10;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int stk[N],top;
int dcc_cnt;
vector<int>dcc[N];
int cut[N],root;
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;
stk[++top]=u;
if(u==root&&h[u]==-1)
{
dcc_cnt++;
dcc[dcc_cnt].pb(u);
return;
}
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(dfn[u]<=low[j])
{
cnt++;
if(u!=root||cnt>1)cut[u]=1;
++dcc_cnt;
int y;
do
{
y=stk[top--];
dcc[dcc_cnt].pb(y);
} while (y!=j);
dcc[dcc_cnt].pb(u);
}
}
else low[u]=min(low[u],dfn[j]);
}
}
signed main()
{
quick_cin();
int T=1;
while(cin>>m,m)
{
rep2(i,1,dcc_cnt)dcc[i].clear();
idx=n=timestamp=top=dcc_cnt=0;
memset(h,-1,h);
memset(dfn,0,dfn);
memset(cut,0,cut);
while(m--)
{
int a,b;
cin>>a>>b;
n=max(n,max(a,b));
add(a,b),add(b,a);
}
for(root=1;root<=n;++root)//这里不能写rep2(root,1,n),会变成局部变量导致错误
{
if(!dfn[root])tarjan(root);
}
int res=0;
ULL num=1;
rep2(i,1,dcc_cnt)
{
int cnt=0;
rep1(j,0,dcc[i].size())
{
if(cut[dcc[i][j]])cnt++;
}
if(cnt==0)
{
if(dcc[i].size()>1)res+=2,num*=dcc[i].size()*(dcc[i].size()-1)/2;
else res++;
}
else if(cnt==1)
{
res++;
num*=dcc[i].size()-1;
}
}
cout<<"Case "<<T++<<": "<<res<<" "<<num<<endl;
}
return 0;
}