Tarjan算法求双连通分量u和v ,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说 u和v 边双连通 。u和v ,如果无论删去哪个点(只能删去一个,且不能删u和v 自己)都不能使它们不连通,我们就说u 和v 点双连通 。x,y 边双连通,y,z 边双连通,则x,z 边双连通。A,B 点双连通,B,C 点双连通,而A,B 不 点双连通。
割点和割边
割点:对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(又称割顶)。
割点的性质:
2;u 如果是割点,当且仅当 u 至少存在一个子树v,v中没有连向 u 的祖先的边(返祖边)。即v访问结束时满足low[v]>=dfn[u]。code
void tarjan(int u,int fa){ //当fa=0时,说明该节点是根节点;
int num=0; //用来记录子节点数;
low[u]=dfn[u]=++Time;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){//v为白点,即(u,v)为树枝边
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(!fa && ++num>1||fa && low[v]>=dfn[u]){
//1.根节点是割点,且子节点数大于等于2;
//2.非根节点是割点,且子节点中没有返祖边;
cutpoint[u]=1; //标记该点为一个割点;
}
}
else if(v!=fa){//返祖边
low[u]=min(low[u],dfn[v]);
}
}
}
割边:对于一个无向图,如果删掉一条边后图中的连通分量数增加了,则称这条边为桥或者割边。
桥的性质:
边(u, v)在dfs 树中。如果u 为v 的父亲,v 的子树中没有向u 或其祖先连的边。
如果桥(u,v)的两个端点都不是叶子节点,则节点u和v为割点。
code
void tarjan(int u,int fa){
bool flag=0; //用来判断是否存在重边
low[u]=dfn[u]=++Time;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);//儿子更新父亲
if(dfn[v]==low[v]){//它的子节点v的子树中,没有像u或其祖先连的边(返祖边)
bridge[i]=bridge[i^1]=1; //边i和i的反向边是桥,边的编号从0开始
}
}
else if(v!=fa || flag){//重边,两个点算环
low[u]=min(low[u],dfn[v]);
}
else flag=1;//反向边置标记为1
}
}
双连通分图
边双连通图:如果任意两点至少存在两条边不重复路径,则称该图为边双连通的。
边双连通图的定义等价于任意一条边至少在一个简单环中。
边连通分量:边双连通的极大子图称为边双连通分量。
边双连通分量的特点
边双连通分量里,一个点有可能出现在多个简单环里,所以我们在当前点u的所有子树访问结束,即变黑后,如果dfn[u]==low[u],从栈顶到u的点均为同一边双连通分量,节点u必然是此边双的最早访问的点。
code
void tarjan(int u,int fa){
bool flag=0;//是否有重边
low[u]=dfn[u]=++Time;
st[++Top]=u;//依次进栈
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){//白点
tarjan(v,u);
low[u]=min(low[u],low[v]);//儿子更新父亲
}
else if(v!=fa || flag){//重边
low[u]=min(low[u],dfn[v]);
}
else flag=1;//反向边置标记为1
} //u的子树全部访问结束,边双缩点
if(low[u]==dfn[u]){
num++;
int tmp;
do{
tmp=st[Top--]; //退栈,原来栈中的元素构成一个边双
belong[tmp]=num;
}while(tmp!=u);
}
}
点双连通图:如果任意两点至少存在两条点不重复的路径,则称这个图为点双连通的(简称双连通);
点双连通图的定义等价于任意两个点都在同一个简单环中。
点双连通分量:点双连通的极大子图称为点双连通分量(简称双连通分量)
点双连通分量的特点:
code
void tarjan(int u,int fa)
{
low[u]=dfn[u]=++Time;
st[++Top]=u;//依次进栈
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(dfn[u]<=low[v]){//如果u为割点,点双缩点
++num; //num表示第几个点双区域(一个图可能存在多个点双)
while(st[top+1]!=v){//从栈顶到v依次出栈
int w=s[top--];//去栈顶并退栈
dcc[num].push_back(w);//节点v属于编号为num的点双
}
dcc[num].push_back(u);//u可以在多个dcc,所以不出栈
}
}
else if(v!=fa){
low[u]=min(low[u],dfn[v]);
}
}
}
双连通分量
cnt,最后答案为(cnt+1)/2强连通分量
DAG图,然后做DAG上的dpDAG的dpTarjan求最大最小环Tarjan算法一般用来强连通分量,它依次访问图中的各个强连通分量,这题要求最大环,而环也是强连通分量的一部分.
在每个点访问其他点时修改时间戳,达到每个环上时间戳连续的目的,这样当访问到一个栈中节点时就能直接更新最大环了。根据同样的思路,即使边权任意,也可求最大环或最小环。
code
void tarjan(int fa,int u){
dfn[u]=low[u]=++Time;
vis[u]=1;s[++top]=u;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(fa==v)continue;
if(!dfn[v]){
int tmp=Time;//先记录父节点u的时间戳
tarjan(u,v);
Time=tmp;//子树v处理完了,让下一个子树时间戳从Time+1开始
low[u]=min(low[u],low[v]);
}else if(ins[v]==1){//发现返祖边
low[u]=min(low[u],dfn[v]);
ans=max(ans,dfn[u]-dfn[v]+1);//环上的点的dfn值是连续的
}
}
原文:https://www.cnblogs.com/hbhszxyb/p/12812434.html