图联通性问题(Tarjan)整理
笑死,根本学不会。(烂尾力
Tarjan算法是用于处理图连通性相关的一类算法。
1、强连通分量、双连通分量、割点与桥的定义
见 OI wiki
2、Tarjan算法的基本思想与框架
本质是通过构建 dfs 生成树,然后处理非树边来求出连通性相关的这些量。
dfs 生成树的标记通过dfn序(dfs到某个点的顺序构建),处理非树边需要一个low值(通过望子树走的树边或者子树上的至多一条非树边能到达的dfn值最小的点)。
void tarjan(int x)
{
low[x]=dfn[x]=++num;
for(int i=H[x];i;i=K[i])
{
int y=V[i];
if(!dfn[y])
tarjan(y,i),low[x]=min(low[x],low[y]);
else
low[x]=min(low[x],dfn[y]);
}
}
3、强连通分量
非树边有三种:回边,横叉边,前向边。
-
前向边对答案无贡献,不作讨论。
-
回边一定有贡献,且满足low值的条件,更新为low。
-
横叉边只有指向能回到dfs树上祖先的点才有贡献。
综上,我们可以用一个栈记录一些节点,这些节点要么是当前点的祖先节点,要么能够到达祖先节点。
每dfs到时节点入栈。
不在栈中的横叉边指向节点不能更新low值,因为这些点回不到祖先,不能产生贡献。
当存在点 \(dfn(x)=low(x)\) 时说明以x为根的子树构成了一个强连通分量,无法从此到达祖先节点,所以x及其上方的节点退栈,并计为一个强连通分量。
void tarjan(int x)
{
low[x]=dfn[x]=++num;
st[++top]=x,ins[x]=1;
for(int i=H[x];i;i=K[i])
{
int y=V[i];
if(!dfn[y])
tarjan(y),low[x]=min(low[x],low[y]);
else if(ins[y])
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
++cnt;
for(;st[top]!=x;)
{
bl[st[top]]=cnt,a2[cnt]+=a[st[top]];
ins[st[top]]=0,--top;
}
bl[x]=cnt,a2[cnt]+=a[x];
ins[x]=0,--top;
}
}
//in main
for(int x=1;x<=n;x++)
if(!dfn[x]) tarjan(x);
4、桥与边双连通分量
\(dfn(x)<low(y)\)
void tarjan(int x,int in_e)
{
low[x]=dfn[x]=++num;
for(int i=H[x];i;i=K[i])
{
int y=V[i];
if(!dfn[y])
{
tarjan(y,i),low[x]=min(low[x],low[y]);
if(low[y]>dfn[x])
{
int u=V[i],v=V[i^1];
if(u>v) swap(u,v);
bdg.push_back(mkp(u,v));
}
}
else if(i!=(in_e^1))
low[x]=min(low[x],dfn[y]);
}
}
//in main
for(int x=1;x<=n;x++)
if(!dfn[x]) tarjan(x,-1);
5、割点与点双连通分量
\(dfn(x) \leq low(y)\)
注意根节点需要两个满足条件的 \(y\)。
void tarjan(int x,int rt)
{
low[x]=dfn[x]=++num;
int flag=0;
for(int i=H[x];i;i=K[i])
{
int y=V[i];
if(!dfn[y])
{
tarjan(y,rt),low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
++flag;
if(x!=rt || flag>1) cut[x]=1;
}
}
else
low[x]=min(low[x],dfn[y]);
}
}