关于tarjan算法的一些整理
要NOIP了,来补一些算法。
主要是为了讲一下tarjan的衍生各种定义,以及讲解一下代码之间的联系与不同,想看算法原理的可自行百度。
当然马蜂也都是很久以前的了,不喜勿喷。
强连通分量
定义(显然仅在有向图中有):一个极大子图满足内部任意一点出发可到其他任一点。
可以用来缩点。
code
一些地方有注释。注意仅有这个代码是没fa
的。
void tarjan(int u)
{
dfn[u]=low[u]=++tim;
vis[u]=1;sta[++top]=u;
for(int i=head[u]; i; i=nxt[i])
{
int v=to[i];
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(vis[v]) low[u]=min(low[u],dfn[v]);
//这里是考虑当单点下面挂着环(环指向点)时,单点与环不能属于同一个分量
}
if(low[u]==dfn[u])
{
scc[u]=++tot;vis[u]=0;
val[tot]=a[u];
while(sta[top]!=u)
{
scc[sta[top]]=tot;
val[tot]=min(val[tot],a[sta[top]]);
vis[sta[top--]]=0;
}
--top;
}
}
割点
定义(这个顺便是有向图还是无向图):删去这个点及其连边,图分为两个不连通的部分。
code
void tarjan(int u,int dad){
dfn[u]=low[u]=++tim;
int k=0;
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].to;
if(!dfn[v]){
k++;tarjan(v,u);
low[u]=min(low[u],low[v]);
if(u!=root&&dfn[u]<=low[v]
||u==root&&k>1)spot[u]=1;
//注意到选定u为根节点时,在原图中,其实多个子树被u分割,因此还需特判。
}
else if(u!=dad)low[u]=min(low[u],dfn[v]);
}
}
桥
定义:删去这条边,图分为两个不连通的部分。
求解思路应该和上面很想,但如果有重边那一定两个都不能是桥,而如果直接写成else if(v!=dad) low[u]=min(low[u],dfn[v]);
判不到这个情况,所以改写成else if(i!=(edge^1)) low[u]=min(low[u],dfn[v]);
即可。
code
void tarjan(int u,int edge){
dfn[u]=low[u]=++tim;
for(int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(!dfn[v])
{
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]
点双连通分量
定义:一个极大的子图,满足图中删去任何一个点都不会改变图的连通性。
性质:
-
点双连通分量中没有割点。
若干个点双连通分量以割点相连接。
原图中的每个割点可能属于多个点双连通分量,其它点只可能属于一个点双连通分量。
-
任意两个端点之间,存在至少两条不相交的简单路径。
很明显,根据求割点的方法分开点双连通分量即可,割点视实际情况处理。
看一道题:
裸题,求点双的size即可。
code
void tarjan(int u,int fa)
{
sta[++top]=u;dfn[u]=low[u]=++tim;
for(int i=head[u]; i; i=nxt[i])
{
int v=to[i];if(v==fa) continue;
if(!dfn[v])
{
tarjan(v,u);
if(low[v]>=dfn[u])
//这里取大于等于是为了处理一个完整环+一个割点+一个完整环的情况。
{
ll sz=2;vis[u]=vis[v]=1;
while(sta[top]!=v) ++sz,vis[sta[top]]=1,--top;
--top;ans=ans+sz*(sz-1)/2ll;
}
else low[u]=min(low[u],low[v]);
}
else low[u]=min(low[u],dfn[v]);
}
}
The End.