Tarjan 与圆方树学习笔记
强连通分量
复习材料:link。
Tarjan 的关键是对于环来说,只有树边、返祖边、横叉边是有用的,前向边没有用。但是组成环的必要条件还是依靠树边与返祖边。
而其中,树边能直接继承儿子的 low 值,但返祖边、横叉边只能继承 dfn 值的原因是只要继承的 dfn 值,此时他就不会被当做一个 SCC 的根节点,而他前面的节点同样会被这个 dfn 值更新到(除了本身就是这个 dfn 值的节点)。
实际上 SCC 的 Tarjan 是可以把返祖边、横叉边继承 dfn 值改成把返祖边、横叉边继承 low 值的。只不过为了便于和其他 Tarjan 模板一起背,这里用 dfn 更新而已。
而不在栈中的横叉边不算(返祖边一定在栈中)的原因是此时这个边连向的点已经处于另一个 SCC 中,而那个 SCC 无法通过任何边走到这个节点的 SCC 的根,自然不能统计答案了。所以这个判断的意义就在于限制了只有在栈中的横叉边或者返祖边才能更新,而前向边是一直没有用的,根本不用更新。
时间复杂度
int scc[N],dfn[N],low[N],stk[N],tp,tot,cnt;
bitset<N>instk;
vector<int>g[N];
void tarjan(int u)
{
low[u]=dfn[u]=++tot;
instk[u]=1;stk[++tp]=u;
for(auto v:g[u])
{
if(dfn[v]==0)
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(instk[v])
{
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u])
{
int v;
cnt++;
do{
v=stk[tp--];
instk[v]=0;
scc[v]=cnt;
}while(u!=v);
}
}
割点
复习材料:link。
无向图与有向图不一样,无向图没有横叉边。在更新 low 的时候只有树边和返祖边会更新,前向边更新了和没更新一样。
注意判断割点的时机,是在走树边的时候。判断条件是
同时求割点是可以走反边来更新的,因为走反边只会被父亲的 dfn 更新,不会影响割点的判定。这也是为啥求割点不能用返祖边的 low 来更新,只能用返祖边的 dfn 来更新的原因,如果用 low 更新那么所有点的 low 都会因为反边追溯到根节点处,就判断不出来割点了。
注意每次进入一个连通块需要更新根节点。
时间复杂度
int root,dfn[N],low[N],tot;
vector<int>g[N];
bitset<N>cut;
void tarjan(int u)
{
dfn[u]=low[u]=++tot;
int child=0;
for(auto v:g[u])
{
if(dfn[v]==0)
{
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])
{
child++;
if(u!=root||child>=2)cut[u]=1;
}
}
else
{
low[u]=min(low[u],dfn[v]);
}
}
}
割边
复习材料:link。
同样没有横叉边,大体和割点一样,判断的条件是
同时因为叶子节点的边可以作为割边,所以不用特判根节点和满足要求的孩子个数,直接判断就能求出割边。
但是不能走反边的限制也让割边的实现有所不同,割边的实现上尽量写链式前向星,这样才能根据节点的编号判断反边(异或
时间复杂度
注意,此处我把返祖边的 dfn 更新 low 值改成把返祖边的 low 值更新 low 值同样可以 AC 模板题,但是暂不清楚此写法是否可靠。
struct Edge{
int v,ne;
}e[N];
int n,m,h[N],idx=1,low[N],dfn[N],cnt,tot;
pi cut[N];
void add(int u,int v)
{
e[++idx]={v,h[u]};
h[u]=idx;
}
void tarjan(int u,int ineg)
{
dfn[u]=low[u]=++tot;
for(int i=h[u];i;i=e[i].ne)
{
int v=e[i].v;
if(dfn[v]==0)
{
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])cut[++cnt]={min(u,v),max(u,v)};
}
else if(i^1^ineg)
{
low[u]=min(low[u],dfn[v]);
}
}
}
这里借董晓 blog 的一张图:
不难发现,这三个模板都有着相同的架构,先走树边,用树边的 low 更新自己的 low,再走横叉边和返祖边,用他们的 dfn 更新自己的 low。
不同之处在于 SCC 中是在一个节点全遍历完,要 return 的时候用弹栈的方式求出 SCC 的。而割点和割边是在走树边的时候进行判断的,并且割点的判断条件是
边双连通分量
复习材料:link。
和求割边代码极其相似,唯一不同的是在进入的时候要把该节点入栈,在离开节点的时候判断该节点是否是一个边双连通分量的根,如果是的话就一直弹栈直到把这个连通分量弹空。整体和 Tarjan 求 SCC 比较像。
此处有割边其实就对应着子节点的一次弹栈。
在处理完边双后,缩完点的图的边全都是原图的割边,同时缩完点后的图形成一棵树(否则有环的话就还可以再缩点)。
时间复杂度
struct Edge{
int v,ne;
}e[M];
int n,m,h[N],idx=1,dfn[N],low[N],tot,stk[N],tp,cnt;
bitset<M>cut;
vector<int>edcc[N];
void add(int u,int v)
{
e[++idx]={v,h[u]};
h[u]=idx;
}
void tarjan(int u,int ineg)
{
dfn[u]=low[u]=++tot;
stk[++tp]=u;
for(int i=h[u];i;i=e[i].ne)
{
int v=e[i].v;
if(dfn[v]==0)
{
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])cut[i]=cut[i^1]=1;
}
else if(i^1^ineg)
{
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u])
{
cnt++;
int v;
do{
v=stk[tp--];
edcc[cnt].push_back(v);
}while(v!=u);
}
}
点双连通分量
复习材料:link。
点双连通分量本质和求割点差不多,只不过在进入节点的时候要入栈,在发现
同时,注意特判孤立点与自环,尤其注意孤立点和自环结合的数据,此时需要记录某个点非自环的出边数量,只有这个值为
点双和边双的区别在于统计连通分量的时候,一个是在离开的时候统计,一个是在走树边的时候统计,这是因为点双所用的点会重复,而边双不会。
点双缩点后,建图是把整个点双看作节点,然后连所有割点向与包含它的点双的边,这个最终形成的形态是一棵树。这其实本质上就是圆方树了。
时间复杂度
int n,m,root,dfn[N],low[N],tot,stk[N],tp,cnt;
vector<int>g[N],vdcc[N];
bitset<N>cut;
void tarjan(int u)
{
low[u]=dfn[u]=++tot;
stk[++tp]=u;
int cb=0,child=0;
for(auto v:g[u])
{
if(dfn[v]==0)
{
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])
{
child++;
if(u!=root||child>=2)cut[u]=1;
cnt++;
int x;
do{
x=stk[tp--];
vdcc[cnt].push_back(x);
}while(x!=v);
vdcc[cnt].push_back(u);
}
}
else
{
low[u]=min(low[u],dfn[v]);
}
if(v!=u)cb++;
}
if(cb==0)vdcc[++cnt].push_back(u);
}
再次借一下图:
这里图里给出的代码的 vdcc 是错误的,他没有特判孤立点与自环结合的情况。
SCC 和边双的共同之处是都是在离开的时候统计连通分量的,但点双是在走树边的时候统计的,并且还要特判自环和孤立点;SCC 与点双的共同之处是都用邻接表建图,而边双使用了链式前向星;边双和点双的共同之处是他们都是无向图,并且缩出来的图是树,而 SCC 是有向图,缩出来的图是 DAG。
__EOF__
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek-R1本地部署如何选择适合你的版本?看这里
· 开源的 DeepSeek-R1「GitHub 热点速览」
· 传国玉玺易主,ai.com竟然跳转到国产AI
· 揭秘 Sdcb Chats 如何解析 DeepSeek-R1 思维链
· 自己如何在本地电脑从零搭建DeepSeek!手把手教学,快来看看! (建议收藏)