tarjan

还记得寒假集训没好好学 $tarjan$ ,一直就不咋会,所以趁着考前重新学了一下。

$tarjan$ 的基本运用主要有:

$1.$ 有向图中将若干点缩成一个点,建出一个 $DAG$ ,搭配各类最短路写法或拓扑排序使用

$code$

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1e4+10;
int n,m;
int a[N];
struct Graph{
    int head[N],tot,cnt,ntime,top,stk[N],low[N],dfn[N],bl[N],d[N],vis[N];
    long long sum[N];
    struct Node{int from,to,nest;}bian[N*10];
    void add(int x,int y){bian[++tot]=(Node){x,y,head[x]};head[x]=tot;d[y]++;}
    void tarjan(int x){
        dfn[x]=low[x]=++ntime;
        vis[x]=1,stk[++top]=x;
        for(int i=head[x];i;i=bian[i].nest){
            int v=bian[i].to;
            if(!dfn[v]){
                tarjan(v);
                low[x]=min(low[x],low[v]);
            }
            else if(vis[v])low[x]=min(low[x],dfn[v]);
        }
        if(dfn[x]==low[x]){
            cnt++;
            do{
                vis[stk[top]]=0;
                bl[stk[top]]=cnt;
                sum[cnt]+=a[stk[top]];
            }while(x!=stk[top--]);
        }
    }
}G1,G2;
void build(){
    for(int i=1;i<=n;i++)if(!G1.dfn[i])G1.tarjan(i);
    for(int i=1;i<=m;i++)if(G1.bl[G1.bian[i].from]!=G1.bl[G1.bian[i].to])G2.add(G1.bl[G1.bian[i].from],G1.bl[G1.bian[i].to]);
}
int q[N],l,r;
long long dis[N],ans;
void topsort(){
    l=1,r=0;
    for(int i=1;i<=G1.cnt;i++){
        if(G2.d[i]==0)q[++r]=i,dis[i]=G1.sum[i],ans=max(ans,dis[i]);
    }
    while(l<=r){
        int cc=q[l++];
        for(int i=G2.head[cc];i;i=G2.bian[i].nest){
            int v=G2.bian[i].to;
            dis[v]=max(dis[v],dis[cc]+G1.sum[v]);
            ans=max(ans,dis[v]);
            G2.d[v]--;
            if(G2.d[v]==0)q[++r]=v;
        }
    }
    printf("%lld\n",ans);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=m;i++){
        int s1=0,s2=0;
        scanf("%d%d",&s1,&s2);
        G1.add(s1,s2);
    }
    build();
    topsort();
    return 0;
}

$2.$ 无向图中求割点或割边(桥)。

$3.$ 点双联通分量与边双联通分量

对于这两部分,我一直记不住,但理解一下就记得很深了。

对于割点的求法,有一句最关键的 $low_v \ge dfn_u$,意味 $v$ 不可能通过其他横叉边或返祖边到达 $u$ 的祖先,意味 $v$ 想到达 $u$ 的祖先一定要经过 $u$,则 $u$ 就是一个割点;对于一开始出发的点,他想要成为一个割点,那么他必然至少有 $2$ 条边。

对于割边的求法,有一句最关键的是 $low_v > dfn_u$,意味 $v$ 不可能通过其他横叉边或返祖边到达 $u$ 的祖先,意味 $v$ 想到达 $u$ 的祖先一定要经过边 $(u,v)$,所以边 $(u,v)$ 为桥。

对于点双与边双,实质上就是求割点与割边的过程,我们可以通过一遍 $tarjan$ 求出所有的割点或割边,再经过一遍 $dfs$ ,不经过割点或割边求出相应的双联通分量。

对于点双,我们也可以通过一遍 $tarjan$ 求出,具体与建圆方树的方法一致,下面的代码写的是这种做法。

对于边双,我们因为无法处理单点为双联通分量的情况,我们可以新建一个超级点,再跑 $tarjan$,也可以用一次 $tarjan$ 求出边双

$code$

割点

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=2e4+10;
int n,m;
struct Graph{
    int head[N],low[N],dfn[N],ntime,tot,cnt,cut[N];
    struct Node{int to,nest;}bian[N<<4];
    void add(int x,int y){bian[++tot]=(Node){y,head[x]};head[x]=tot;}
    void tarjan(int x,int fa){
        dfn[x]=low[x]=++ntime;
        int kid=0;
        for(int i=head[x];i;i=bian[i].nest){
            int v=bian[i].to;
            if(!dfn[v]){
                kid++;
                tarjan(v,x);
                low[x]=min(low[x],low[v]);
                if(low[v]>=dfn[x])if(x!=fa&&!cut[x])cut[x]=1,cnt++;
            }
            else if(v!=fa)low[x]=min(low[x],dfn[v]);
        }
        if(x==fa&&kid>=2&&!cut[x])cut[x]=1,cnt++;
    }
}G;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int s1=0,s2=0;
        scanf("%d%d",&s1,&s2);
        G.add(s1,s2),G.add(s2,s1);
    }
    for(int i=1;i<=n;i++)if(!G.dfn[i])G.tarjan(i,i);
    printf("%d\n",G.cnt);
    for(int i=1;i<=n;i++)if(G.cut[i])printf("%d ",i);
    return 0;
} 

割边(因为下面边双有写,我就不再写了)

点双

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=5e5+10;
int n,m;
struct Graph{
    int head[N],tot,ntime,cnt,dfn[N],low[N],stk[N],top;
    vector<int>vDCC[N];
    struct Node{int to,nest;}bian[N<<3];
    void add(int x,int y){bian[++tot]=(Node){y,head[x]};head[x]=tot;}
    void tarjan(int x,int fa){
        int son=0;
        dfn[x]=low[x]=++ntime;
        stk[++top]=x;
        for(int i=head[x];i;i=bian[i].nest){
            int v=bian[i].to;
            if(!dfn[v]){
                son++;
                tarjan(v,x);
                low[x]=min(low[x],low[v]);
                if(low[v]>=dfn[x]){
                    cnt++;
                    do{vDCC[cnt].push_back(stk[top]);}while(v!=stk[top--]);
                    vDCC[cnt].push_back(x);
                }
            }
            else if(v!=fa)low[x]=min(low[x],dfn[v]);
        }
        if(!son&&!fa)++cnt,vDCC[cnt].push_back(x);
    }
}G;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int s1=0,s2=0;
        scanf("%d%d",&s1,&s2);
        G.add(s1,s2),G.add(s2,s1);
    }
    for(int i=1;i<=n;i++)if(!G.dfn[i])G.tarjan(i,0);
    printf("%d\n",G.cnt);
    for(int i=1;i<=G.cnt;i++,putchar('\n')){
        printf("%u ",G.vDCC[i].size());
        for(auto v : G.vDCC[i])printf("%d ",v);
    }
    return 0;
}

边双

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=5e5+10;
int n,m;
struct Graph{
    int head[N],cnt,tot,ntime,dfn[N],low[N],vis[N],is_bridge[N<<3];
    vector<int>eDCC[N];
    Graph(){tot=1;}
    struct Node{int to,nest;}bian[N<<3];
    void add(int x,int y){bian[++tot]=(Node){y,head[x]};head[x]=tot;}
    void tarjan(int x,int fa){
        dfn[x]=low[x]=++ntime;
        for(int i=head[x];i;i=bian[i].nest){
            int v=bian[i].to;
            if(!dfn[v]){
                tarjan(v,x);
                low[x]=min(low[x],low[v]);
                if(low[v]>dfn[x]){
                    is_bridge[i]=1;
                    is_bridge[i^1]=1;
                }
            }
            else if(v!=fa)low[x]=min(low[x],dfn[v]);
        }
    }
    void dfs(int x){
        vis[x]=1;
        eDCC[cnt].push_back(x);
        for(int i=head[x];i;i=bian[i].nest){
            if(is_bridge[i])continue;
            int v=bian[i].to;
            if(vis[v])continue;
            dfs(v);
        }
    }
    void get(){
        for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,0);
        for(int i=1;i<=n;i++)if(!vis[i])cnt++,dfs(i);
    }
}G;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int s1=0,s2=0;
        scanf("%d%d",&s1,&s2);
        G.add(s1,s2),G.add(s2,s1);
    }
    G.get();
    printf("%d\n",G.cnt);
    for(int i=1;i<=G.cnt;i++,putchar('\n')){
        printf("%u ",G.eDCC[i].size());
        for(auto j:G.eDCC[i])printf("%d ",j);
    }
    return 0;
}

原文地址:http://www.cnblogs.com/hxqasd/p/16827716.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性