tarjan算法的各类应用
前言:
学习tarjan也有这么长时间了,想尝试着写篇博客来记下自己对tarjan的理解。
PS:因为在下的能力有限,对tarjan的理解还不够深刻,所以会继续钻研tarjan,这篇博客会作为我的一个笔记本,不断的更新。
求强连通分量
既然我们要求强连通分量,那么我们得先弄清,什么是强连通分量?
在有向图中,如果两个顶点u,v间有一条路(u,v),也有一条路(v,u),那么则称这两个顶点是强连通的。
如果有向图的任意两个顶点都强连通,则称该图是一个强连通图。
有向非强连通图中的强连通子图,称为强连通分量。
求割点
什么是割点?
在一个无向连通图中,如果有一个点集合,删去这个点集合后,原图变得不连通,就称这个点集合为割点集合。
这里我们再引进来一个概念:
一个图的点连通度:最小割点集合中的元素个数。
如果一个无向连通图的点连通度大于一,则称这个图是点双联通的,简称双连通或重连通。
当且仅当一个无向连通图的点连通度为一,那么该图的最小割点集合中,唯一的那个元素,唯一的那条点,叫做割点。
割点又该如何判断呢?
当一个顶点x是割点时,它只需满足:
- u为树根,且u的子树个数大于1。
- u不为树根,且满足(u,v)为树枝边,并使得dfn[u]<=low[v]。
以上两点只需满足一个就行了。
第一点我们很好理解,当这个点为树根,且有多个子树时,割去这个树根点,剩下的图自然不会联通。在程序中我们可以表现为:
if(u==father)++cnt;
if(u==father&&cnt>1)cut[u]=1;
第二点也还行,其实就是u在搜索树中为v的父亲,或者说u和v之间有一条边,且u的dfs序要小于等于v的祖先的dfs序,就等于在搜索的时候,是先搜索了u,再搜索了v的祖先(甚至u就是v的祖先),所以删去u之后,v以及v的子树都无法连接到u的祖先,如此一来,u自然也是个割点。在程序中我们可以表示为:
if(u!=father&&dfn[u]<=low[v])cut[u]=1;
到这里,割点判断的核心内容已经差不多了,但是由割点衍生出来的许多题型都需要我们从这最基础的上面再加一些骚操作。
在下实力也有限,无法具体指出来,只能靠各位多做题多总结,找到方法,提升熟练度了。
割点tarjan的模板如下:
void tarjan(int u,int father)
{
dfn[u]=low[u]=++num;
st[++top]=u;
int cnt=0;
for(int i=first[u];i;i=nex[i])
{
int v=to[i];
if(!dfn[v])
{
tarjan(v,father);
low[u]=min(low[u],low[v]);
if(u!=father&&dfn[u]<=low[v])
cut[u]=1;
if(u==father)++cnt;
}
low[u]=min(low[u],dfn[v]);
}
if(cnt>=2&&u==father)
cut[u]=1;
}
求割边与桥
什么是割边?什么是桥?
在一个无向连通图中,如果有一个边集合,删去这个边集合后,原图变得不连通,就称这个边集合为割边集合。
这里我们再引进来一个概念:
一个图的边连通度:最小割边集合中的元素个数。
如果一个无向连通图的边连通度大于一,则称这个图是边双联通的,简称双连通或重连通。
当且仅当一个无向连通图的边连通度为一,那么该图的最小割边集合中,唯一的那个元素,唯一的那条边,叫做桥。
在很多的题目中,我们都会遇到需要求一张图的桥的情况,不妨开动脑经想想,怎么样求一个桥?
判断桥的条件如下:
若一条无向边(u,v)是桥,则当且仅当(u,v)是树枝边,且满足dfn[u]<low[v]。
这句话怎么理解?
dfn[u]是u的dfs序,low[v]是v的祖先的dfs序,当dfn[u]<low[v]时,则代表先搜索u,再搜索v的祖先,v想要到达u的祖先就必须经过(u,v)这条边,删去(u,v),该图便不会再联通。
现实情况中,可能会有重边的情况出现,所以我们就用一个father变量代表这条边的起始,再加以判断,防止它在重边时再次进行low[]值的更换。
代码实现如下:
void tarjan(int u,int father)
{
dfn[u]=low[u]=++num;
for(int i=first[u];i;i=next[i])
{
int v=to[i];
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v])
{
bridge[++cnt][0]=u;
bridge[cnt][1]=v;
}
}
else if(v!=father)
low[u]=min(low[u],dfn[v]);
}
}