连通性问题(无向图)(未完结)
边双连通分量
我们首先定义两种边:返祖边为从一个点指向其祖先的边;横叉边从某个点指向树中另一个子树中的点的边。两者统称为非树边。而剩下的边即为树边,树边也就是再搜索树上的边。
考虑设 \(dfn_i\) 为点 \(i\) 是第几个被搜索到的,\(low_i\) 表示点 \(i\) 通过走若干条边之后,再走一条返祖边能到达的 \(dfn\) 最小的节点的 \(dfn\)。
如果对于一个点 \(u\) 的儿子 \(v\),满足 \(low_v>dfn_u\),则 \(v\) 不经过与 \(u\) 之间的边,就跳不到 \(u\) 上面的点,所以这是一条割边。
代码:
void tar(int u,int fa){
dfn[u]=low[u]=++tot;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!dfn[j]){
tar(j,i);
if(low[j]>dfn[u]){
bri[i]=bri[i^1]=1;
}
low[u]=min(low[u],low[j]);
}
else if(i!=(fa^1))low[u]=min(low[u],dfn[j]);
}
}
接下来考虑一个事情,两个边双连通分量之间显然不能经过割边,于是我们跑一个 \(dfs\),从一个点出发不经过割边能到达的点就与其在一个边双连通分量内。
\(dfs\) 代码:
void dfs(int u,int c){
col[u]=c;
res[c].push_back(u);
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(col[j]||bri[i])continue;
dfs(j,c);
}
}
点双连通分量
如果对于一个点 \(u\) 的儿子 \(v\),满足 \(low_v\ge dfn_u\),且 \(u\) 不是这个连通分量的根或者有至少两个儿子,则 \(u\) 为割点。
求割点代码:
void tar(int u,int fa){
dfn[u]=low[u]=++tot;
int cnt=0;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!dfn[j]){
tar(j,fa);
low[u]=min(low[u],low[j]);
if(low[j]>=dfn[u]){
cnt++;
if(u!=fa||cnt>1)cut[u]=1;
}
}
else low[u]=min(low[u],dfn[j]);
}
}
首先注意,割点属于多个点双连通分量内。
我们考虑用一个栈记录之前经过的点,如果发现当前点 \(v\) 的父亲 \(u\) 是割点,则不断弹栈,直到把 \(v\) 弹出,弹出去的点和 \(u\) 则同属一个点双连通分量。
注意特判孤立点。
代码:
void tar(int u,int fa){
dfn[u]=low[u]=++tot;
stk[++top]=u;
int son=0;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!dfn[j]){
son++;
tar(j,u);
low[u]=min(low[u],low[j]);
if(low[j]>=dfn[u]){
cnt++;
int y;
do{
y=stk[top--];
res[cnt].push_back(y);
}while(stk[top+1]!=j);
res[cnt].push_back(u);
}
}
else{
low[u]=min(low[u],dfn[j]);
}
}
if(fa==0&&son==0)res[++cnt].push_back(u);
}