[关于几个tarjan算法]

[关于几个tarjan算法]


首先要搞清楚的是dfn和low两个数组的含义。

dfn是时间戳,表示dfs下第一次访问的时间,然后我们tarjan搜索树上的dfs序就是dfn值。

然后low就是追溯值,表示一下节点dfn最小值:

  1. 子树节点;
  2. 通过一条不在搜索树上的边,能达到子树的节点

然后我们去考虑low[x]怎么算,根据上面的概念,我们不难推出

1.边是搜索树上的边,y是x的子节点:low[x]=min(low[x],low[y]) 
2.边不是搜索树上的边:low[x]=min(low[x],dfn[y])

然后就可以去搞几个tarjan算法了

1.割边/桥

桥的定义是删掉之后G分裂成两个不相连的子图

桥的判定就是对于y是x的儿子,dfn[ x ] < low[ y ] 。然后这个的理解就是从subtree出发到不了比他更早访问的边了,然后需要注意的是因为是大于,我们还需要记一下入边,这样才能判定

  • 代码大概长这样
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=4e5+5;
int hed[N],to[M],nxt[M],cnt=1;
void adde(int u,int v,int w){
    cnt++;to[cnt]=v,nxt[cnt]=hed[u];
}
int num,dfn[N],low[N],bridge[N];
void tarjan(int u,int pre){
    dfn[u]=low[u]=++num;
    for(int i=hed[u];i;i=nxt[i]){
        int v=to[i];
        if(i==pre^1||i==pre)continue;
        if(!dfn[v]){
            tarjan(v,i);
            low[u]=min(low[u],low[v]);
            if(low[y] > dfn[x])
                bridge[i]=bridge[i^1]=true;
        }else low[u]=min(low[u],dfn[v]);
    }
}

2.割点

定义是一个点和连接它的边删了之后G分裂成两个不连通子图

和桥相似,然后注意一下我们的判定是low[y]>=dfn[x]。因为是大于等于,我们就不用记pre了。

还需要注意一下的是我们对于根节点需要特判,就是如果有两个子节点(也可以当成两个节点满足low[y]>=dfn[x])才能当成割点。

  • 代码长这样:
#include<bits/stdc++.h>
using namespace std;
int hed[N],to[M],nxt[M],cnt=1,dfn[N],low[N],cut[N];
inline void adde(int u,int v){
    cnt++;to[cnt]=v,nxt[cnt]=hed[u];hed[u]=cnt;
}
int stk[N],top,root;
inline void tarjan(int u){
    dfn[u]=low[u]=++num;
    stk[++top]=x;
    if(x==root&&hed[x]==0){
        dcc[++dcnt].push_back(x);
        return;
    }
    int flag=0;
    for(int i=hed[u];i;i=nxt[i]){
        int v=to[i];
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[x]){
                flag++;
                if(x!=root||flag>1)cut[x]=true;
            }
        }
    }
}

3.e_DCC:边双联通分量及其缩点

一条边是边双联通图就是不存在桥的时候,然后极大边双联通图就是边双联通分量。

首先求桥是肯定的,然后我们可以像强连通分量什么的一样开个栈来搞,也可以直接dfs一遍就完了。

缩点后原图就是一棵树,然后连的边显然就是桥

  • 代码(namespace真好用)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=2e6+6;
int n,m;
int dccno[N],dcc;
namespace tree{
    int hed[N],to[N*2],nxt[N*2],cnt;
    inline void adde(int u,int v){
        ++cnt;to[cnt]=v,nxt[cnt]=hed[u];hed[u]=cnt;
    }
    
}

namespace graph{
    int hed[N],to[M],nxt[M],cnt=1,dfn[N],low[N],num;
    bool bridge[N];
    inline void adde(int u,int v){
        ++cnt;to[cnt]=v,nxt[cnt]=hed[u];hed[u]=cnt;
    }

    inline void tarjan(int u,int pre){
        low[u]=dfn[u]=++num;
        for(int i=hed[u];i;i=nxt[i])if(i!=(pre^1)){
            int v=to[i];
            if(!dfn[v]){
                tarjan(v,i);
                low[u]=min(low[u],low[v]);
                if(low[v]>dfn[u])bridge[i]=bridge[i^1]=true;
            }
            else low[u]=min(low[u],dfn[v]);
        }
    }

    inline void dfs(int x,int id){
        dccno[x]=id;
        for(int i=hed[x];i;i=nxt[i]){
            int v=to[i];
            if(dccno[v]||bridge[i])continue;
            dfs(v,id);
        }
    }
}
int u[M],v[M];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&u[i],&v[i]);
        graph::adde(u[i],v[i]);
        graph::adde(v[i],u[i]);
    }
    for(int i=1;i<=n;i++)if(!graph::dfn[i])
        graph::tarjan(i,0);
    for(int i=1;i<=n;i++)if(!dccno[i])
        graph::dfs(i,++dcc);
    for(int i=1;i<=m;i++)if(dccno[u[i]]!=dccno[v[i]]){
        tree::adde(dccno[u[i]],dccno[v[i]]);
        tree::adde(dccno[v[i]],dccno[u[i]]);
    }
}

4.v_DCC点双连通分量

把上文的桥换成割点就是定义。

然后这个的联通分量很好求就是直接开个栈去跑,然后因为割点可能属于多个点双,我们只有开个vector去存点双。

然后缩点的话我们直接缩点是不行的,需要把每个割点单独拉出来,然后去用割点连接包含他的所有点双。然后我们的就可以将这个东西缩成一个树了。

  • 代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=2e6+5;
int dcnt;
vector<int> dcc[N];
int cut[N],c[N];
int new_id[N];
namespace DCC{
    int hed[N*2],to[M],nxt[M],cnt;
    inline void adde(int u,int v){
        cnt++;ro[cnt]=v,nxt[cnt]=hed[u];hed[u]=cnt;
    }
    inline void build(){
        int num=dcnt;
        for(int i=1;i<=n;i++)
            if(cut[i])new_id[i]=++num;
        tc=1;
        for(int i=1;i<=cnt;i++){
            for(int j=0;j<dcc[i].size();j++){
                int x=dcc[i][j];
                if(cut[x])adde(i,new_id[x]),adde(new_id[x],i);
                else c[x]=i;
            }
        }
    }
}

namespace graph{
    int hed[N],to[M],nxt[M],cnt=1,dfn[N],low[N];
    inline void adde(int u,int v){
        cnt++;to[cnt]=v,nxt[cnt]=hed[u];hed[u]=cnt;
    }
    int stk[N],top,root;
    inline void tarjan(int u){
        dfn[u]=low[u]=++num;
        stk[++top]=x;
        if(x==root&&hed[x]==0){
            dcc[++dcnt].push_back(x);
            return;
        }
        int flag=0;
        for(int i=hed[u];i;i=nxt[i]){
            int v=to[i];
            if(!dfn[v]){
                tarjan(v);
                low[u]=min(low[u],low[v]);
                if(low[v]>=dfn[x]){
                    flag++;
                    if(x!=root||flag>1)cut[x]=true;
                    dcnt++;
                    int z=-1;
                    do{
                        z=stk[top--];
                        dcc[dcnt].push_back(z);
                    }while(z!=v);
                    dcc[dcnt].push_back(x);
                }
            }
        }
    }
    
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int u,v;scanf("%d%d",&u,&v);
        graph::adde(u,v);graph::adde(v,u);
    }
    for(int i=1;i<=n;i++)if(!dfn[i]){
        top=0,root=i;
        graph::tarjan(i);
    }
    DCC::build();
}

5.有向图强连通分量

这个不用多说了吧,注意一下有向图可以两个都用low[y]更新low[x]就好了

缩点的话就直接for边然后去重就行

  • 代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,M=5e4+5;
int n,m;
int hed[N],nxt[M],to[M];
int cnt;
void adde(int u,int v){
    cnt++;nxt[cnt]=hed[u],to[cnt]=v;hed[u]=cnt;
}
int co[N],low[N],dfn[N],si[N];
int col,num;
int st[N+M],top=0;
void Tarjan(int u){
    dfn[u]=low[u]=++num;
    st[++top]=u;
    for(int i=hed[u];i;i=nxt[i]){
        int v=to[i];
        if(!dfn[v]){
            Tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!co[v])
                low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        co[u]=++col;
        ++si[col];
        while(st[top]!=u){
            ++si[col];
            co[st[top]]=col;
            top--;
        }
        --top;
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1,u,v;i<=m;i++){
        scanf("%d%d",&u,&v);
        adde(u,v);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])Tarjan(i);
}

 

LCA(最近公共祖先)是指在一棵树中,找到两个节点的最近的共同祖先节点。而Tarjan算法是一种用于求解强连通分量的算法,通常应用于有向图中。它基于深度优先搜索(DFS)的思想,通过遍历图中的节点来构建强连通分量。Tarjan算法也可以用于求解LCA问题,在有向无环图(DAG)中。 具体来说,在使用Tarjan算法求解LCA时,我们需要进行两次DFS遍历。首先,我们从根节点开始,遍历每个节点,并记录每个节点的深度(即从根节点到该节点的路径长度)。然后,我们再进行一次DFS遍历,但这次我们在遍历的过程中,同时进行LCA的查找。对于每个查询,我们将两个待查询节点放入一个查询列表中,并在遍历过程中记录每个节点的祖先节点。 在遍历的过程中,我们会遇到以下几种情况: 1. 如果当前节点已被访问过,说明已经找到了该节点的祖先节点,我们可以更新该节点及其所有后代节点的祖先节点。 2. 如果当前节点未被访问过,我们将其标记为已访问,并将其加入到查询列表中。 3. 如果当前节点有子节点,我们继续递归遍历子节点。 最终,对于每个查询,我们可以通过查询列表中的两个节点的最近公共祖先节点来求解LCA。 需要注意的是,Tarjan算法的时间复杂度为O(V+E),其中V为节点数,E为边数。因此,对于大规模的树结构,Tarjan算法是一种高效的求解LCA问题的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值