浅谈双联通分量
附带板子
强联通分量
在学习之前,首先要知道什么是强联通分量以及tarjan算法(当年我学强联通分量的博客
顺便附强联通分量tarjan的代码QwQ
inline void tarjan(int pos)
{
dfn[pos]=low[pos]=++cnt;
s.push(pos);
ins[pos]=1;
for(int i=head[pos];i;i=e[i].nxxt)
{
int to=e[i].to;
if(!dfn[to])
{
tarjan(to);
low[pos]=min(low[to],low[pos]);
}
else if(ins[to])//返祖边
{
low[pos]=min(dfn[to],low[pos]);
}
}
if(dfn[pos]==low[pos])//是强联通分量
{
int t;
do
{
t=s.top();
s.pop();
ins[t]=0;
}while(t!=pos);
}
}
双联通分量
那我们来进入主题
双联通分量(Biconnected Component)下文简称BCC
双联通分量按照性质分为以下两种
- 点双连通分量
- 边双联通分量
- 点双连通分量
在这里介绍一个知识点,割点。
也就是说,你把一个联通图中的一个点拿掉,这个图就变成了两个联通图,那这个点就是割点啦。
那么什么叫双联通呢
若一个无向图中的去掉任意一个节点都不会改变此图的连通性,即不存在割点,则称作点双连通图。一个无向图中的每一个极大点双连通子图称作此无向图的点双连通分量。 ——百度
所以求割点就成了重要的一步.
显然 割点有以下几种性质
4. 根结点u为割顶当且仅当它有两个或者多个子结点
5. 非根结点u为割顶当且仅当u存在结点v,使得v极其所有后代都没有反向边可以连回u的祖先
那么我们求割点就格外简单了,在之前的tarjan算法上做相应的改进,直接附代码
inline void tarjan(int pos,int fa)
{
dfn[pos]=low[pos]=++tot;
int ch=0;
for(int i=head[pos];i;i=e[i].nxxt)
{
int to=e[i].to;
if(!dfn[to])
{
ch++;
tarjan(to,pos);
low[pos]=min(low[pos],low[to]);
if(low[to]>=dfn[pos]) iscut[pos]=1;//只指向自己的子树
}
else if(dfn[to]<dfn[pos]&&to!=fa)//返祖边
{
low[pos]=min(dfn[to],low[pos]);
}
if(!fa&&ch==1) iscut[pos]=0;//根节点只有一个儿子
}
}
那么求点双连通分量的主要就是求割点啦,我们把遍历过的边存到一个pair里,直到遇到了割顶,那么割顶之下的边就都是一个点连通分量了,我们要一直弹到fa->pos这条边。
#define N 100010
vector<int>bcc[N],e[N];
pair<int,int>s[N];
int cnt;
void tarjan(int pos,int fa)
{
dfn[pos]=low[pos]=++tot;
for(int i=0;i<=e[pos].size();i++)
{
int to=e[pos][i],top=stop;//记录fa->pos的边
if(to!=fa&&dfn[to]<dfn[pos])
s[++stop]=make_pair(pos,to);//将边放进pair中
if(!dfn[to])
{
tarjan(to,pos);
low[pos]=min(low[to],low[pos]);
if(dfn[pos]<=low[to])//如果是割顶
{
cnt++;
for(;stop>top;stop--)
{
int s=s[stop].first,t=s[stop].second;
if(bcc[s].empty||bcc[s].back()!=cnt) bcc[s].push_back(cnt);//如果不在编号的cnt中bcc,就赋值
if(bcc[t].empty||bcc[t].back()!=cnt) bcc[t].push_back(cnt);
}
}
}
else if(to!=fa) low[pos]=min(low[pos],dfn[to]);
}
}
边双连通分量
求桥的方法和割点类似,如果节点pos的后代只能指回pos的儿子to,那么截断了pos->to这条边后图就不联通了,即找到了桥。
inline void addbridge(int u,int v)
{
if(u>v) swap(u,v);
bridgex[++cnt]=u;
bridgey[cnt]=v;
}
inline void tarjan(int pos,int fa)
{
dfn[pos]=dfn[pos]=++tot;
for(int i=head[pos];i;i=e[i].nxxt)
{
int to=e[i].to;
if(!dfn[to])
{
tarjan(to,pos);
low[pos]=min(low[pos],low[to]);
if(low[to]>dfn[pos]) addbridge(pos,to);//这就是桥
}
else if(dfn[to]<dfn[pos]&&to!=fa)//返祖边
{
low[pos]=min(dfn[to],low[pos]);
}
}
}
求边双连通就是求桥,我们把点放进一个栈里,知道发现了桥,我们就把栈里的元素往出弹,一直弹到当前结点,就求出了一个边双连通分量。
#define N 100010
vector<int>bcc[N],e[N];
pair<int,int>s[N];
int cnt;
void tarjan(int pos,int fa)
{
bool f=0;
dfn[pos]=low[pos]=++tot;
s[++top]=pos;
for(int i=0;i<=e[pos].size();i++)
{
int to=e[pos][i];
if(!dfn[to])
{
tarjan(to,pos);
low[pos]=min(low[to],low[pos]);
}
else if(to!=fa||flag) low[pos]=min(low[pos],dfn[to]);//返祖边
else f=1;//第一次返回父亲算树边
}
if(dfn[pos]==low[pos])//<-是桥
{
int t;
cnt++;
do
{
t=s[top];
top--;
bcc[t]=cnt;
}while(t!=pos);
}
}
qwq有没有很抽象
我会日后改进的!!