连通性相关¶
约 527 个字 197 行代码 预计阅读时间 4 分钟
强连通¶
有向图,任意两点连通
强连通分量缩点¶
vector<int> e[N], sta, ne[N];
array<int,2> ed[N];
int dfn[N], low[N], f[N], vis[N],in[N],dfsOrder, sccnum;
void init(int n)
{
dfsOrder = sccnum = 0;
for (int i = 1; i <= n; ++i)
e[i].clear();
sta.clear();
memset(vis, 0, sizeof(int) * (n + 1));
memset(dfn,0,sizeof(int)*(n+1));
memset(in,0,sizeof(int)*(n+1));
}
void tarjan(int u)
{
dfn[u] = low[u] = ++dfsOrder;
sta.push_back(u);
vis[u] = 1;
for (auto &v : e[u])
{
if (!dfn[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (vis[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] != low[u])
return;
++sccnum;
vector<int>::iterator it=(--sta.end());
for(f[u]=sccnum,vis[u]=0;*it!=u;--it)
f[*it]=sccnum,vis[*it]=0;
sta.erase(it,sta.end());
}
void makeNewMap(int n,int m)
{
for(int i=1;i<=n;++i)
if(!in[i])
tarjan(i);
for(int i=1;i<=n;++i)
if(!dfn[i])
tarjan(i);
for (int i = 0;i<m;++i)
{
int u=f[ed[i][0]],v=f[ed[i][1]];
if(u==v)
continue;
ne[u].push_back(v);
}
}
割点¶
如果删除一个点后,这个图的极大连通分量数增加,那么该点为割点
记录每个点的dfs序(dfn),和不通过其父亲能到达的最小dfs序(low)
如果点u的一个儿子v,不能回到祖先(low_v\ge dfn_u),那么u为割点
但是对于起点不适用,起点需要有至少2个儿子,才是割点
vector<int> e[N];
int dfn[N],low[N],dfsOrder,cut[N];
void tarjan(int u,int fa)
{
low[u]=dfn[u]=++dfsOrder;
int child=0;
for(int &v:e[u])
{
if(!dfn[v])
{
++child;
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(u!=fa && low[v]>=dfn[u])
cut[u]=1;
}
else if(v!=fa)//有向图改为else
low[u]=min(low[u],dfn[v]);
}
if(fa==u && child>=2)
cut[u]=1;
}
void findCutVertex(int n)
{
for(int i=1;i<=n;++i)
{
if(!dfn[i])
tarjan(i,i);
}
}
割边¶
删边,其他类似割点
修改一处:low_v>dfn_u,且不需要特殊处理根节点
u-bridge[u] 是一条割边
一些性质¶
割边的两边一般是割点,除非只有一个点
vector<int> e[N];
int dfn[N],low[N],dfsOrder,bridge[N];
void tarjan(int u,int fa)
{
low[u]=dfn[u]=++dfsOrder;
for(int &v:e[u])
{
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])
bridge[v]=u;
}
else if(v!=fa)//有向图改为else
low[u]=min(low[u],dfn[v]);
}
}
void findBridge(int n)
{
for(int i=1;i<=n;++i)
{
if(!dfn[i])
tarjan(i,i);
}
}
边双连通¶
没有割边的连通图是边双连通,点的边双连通具有传递性
边双连通分量缩点¶
tarjan访问每个点时把点进栈,当一个点 u 的后续点都访问完时,如果 u 是一个极大边双连通分量的,第一个访问的点,把栈内的点都拿出来,直到 u。同时给原来的点做一个映射。同一个映射的点,在同一个边双
新图可能需要旧图的信息,所以旧图的边信息存两份
vector<int> e[N],sta,ne[N];
array<int,2> ed[N];
int dfn[N],low[N],f[N],dfsOrder,bccnum;
void init(int n)
{
dfsOrder=bccnum=0;
for(int i=1;i<=n;++i)
e[i].clear();
sta.clear();
}
void tarjan(int u,int fa)
{
low[u]=dfn[u]=++dfsOrder;
sta.push_back(u);
for(int &v:e[u])
{
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
}
else if(v!=fa)//有向图改为else
low[u]=min(low[u],dfn[v]);
}
if(low[u]!=dfn[u])
return;
++bccnum;
while(bcc.back()!=u)
{
f[bcc.back()]=bccnum;
bcc.pop_back();
}
f[u]=bccnum;
bcc.pop_back();
}
void makeNewMap(int m)
{
tarjan(1,1);
for(int i=0;i<m;++i)
{
int u=f[ed[i][0]],v=f[ed[i][1]];
if(u==v)
continue;
ne[u].push_back(v);
}
}
点双连通¶
没有割点的连通图是点双连通,点的点双连通不具有传递性
除了仅包含两个点一条边的点双外,其他点双都满足:任意两点间都存在至少两条点不重复路径(起点终点除外)
任意一个不是割点的点都只存在于一个点双中,割点也一定属于两个及以上的点双
两个点双至多存在一个公共点——割点
点双连通分量缩点¶
vector<int> e[N], sta;
int dfn[N], low[N], dfsOrder;
vector<vector<int>> ans;
void tarjan(int u, int fa)
{
low[u] = dfn[u] = ++dfsOrder;
sta.push_back(u);
for (auto &v : e[u])
if (!dfn[v])
{
tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u])
{
vector<int> tmp;
while (sta.back() != v)
{
tmp.push_back(sta.back());
sta.pop_back();
}
sta.pop_back();
tmp.push_back(v);
tmp.push_back(u);
ans.push_back(tmp);
}
}
else if (v != fa) // 有向图改为else,重边需判断是否同一条无向边
low[u] = min(low[u], dfn[v]);
}
void makeNewMap(int n)
{
for (int i = 1; i <= n; ++i)
if (!dfn[i])
{
tarjan(i, -1);
if (e[i].empty())//需去除自环
{
vector<int> tmp;
tmp.push_back(i);
ans.push_back(tmp);
}
}
}
圆方树¶
原来的每个点对应一个圆点,每一个点双对应一个方点
对于每一个点双,它对应的方点向这个点双中的每个点连边
如果原图连通,则“圆方树”才是一棵树