zoukankan      html  css  js  c++  java
  • tarjan

    tarjan算法详解直通车

    什么是缩点?

    当我们在求出强连通分量之后,我们可以把同一个强连通分量里的点当成新图里的一个新点,然后如果原图中两个不在同一个强连通分量的点有连边的话,那么他们所对应的新点也有连边。

    我们可以知道,新图一定是一个有向无环图(DAG)。

    void Shrink_point() {
        for(int i=1; i<=n; i++)
            for(int j=head[i]; j; j=edge[j].pre) {
                int to=edge[j].to;
                if(col[i]!=col[to])
                    add(col[i],col[to]);
            }
    }

    P3387 【模板】缩点

    题目背景

    缩点+DP

    题目描述

      给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

    允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

    输入输出格式

    输入格式:

    第一行,n,m

    第二行,n个整数,依次代表点权

    第三至m+2行,每行两个整数u,v,表示u->v有一条有向边

    输出格式:

    共一行,最大的点权之和。

    输入输出样例

    输入样例#1:
    2 2
    1 1
    1 2
    2 1
    输出样例#1:
    2

    说明

    n<=10^4,m<=10^5,|点权|<=1000

    算法:Tarjan缩点+DAGdp

    /*----------------------------------------------------*/

    #样例:

    10 20
    970 369 910 889 470 106 658 659 916 964
    3 2
    3 6
    3 4
    9 5
    8 3
    5 8
    9 1
    9 7
    9 8
    7 5
    3 7
    7 8
    1 7
    10 2
    1 10
    4 8
    2 6
    3 1
    3 5
    8 5

    #输出:

    6911

    /*----------------------------------------------------*/

    题解:

      Tarjan缩点+最短路(spfa)

      为何缩点?—— 因为可以重复经过点,所以一个点所在的强联通分量必定可以到达,所以直接缩点即可

      暴力从每个点跑spfa,取最大权值

    代码:

    #include<queue>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    using namespace std;
    
    const int M = 100010;
    int n,m,dis[M],value[M],ans,num_edge,dfn[M],low[M];
    int Right[M],head[M];
    struct Edge {
        int pre,to,len;
    } edge[M*4];
    
    void add(int u,int v) {
        edge[++num_edge].pre = head[u];
        edge[num_edge].to = v;
        head[u]=num_edge;
    }
    
    int numb_edge,head1[M];
    struct Edge1 {
        int pre,to,len;
    } edge1[M*3];
    
    void add1(int u,int v) {
        edge1[++numb_edge].pre = head1[u];
        edge1[numb_edge].to = v;
        head1[u]=numb_edge;
    }
    
    int dfs_num,stack[M],top,col[M],col_num;
    bool vis[M];
    void tarjan(int x) {
        dfn[x]=low[x]=++dfs_num;
        stack[++top]=x;
        vis[x]=1;
        for(int i=head[x]; i; i=edge[i].pre) {
            int to=edge[i].to;
            if(!dfn[to]) {
                tarjan(to);
                low[x]=min(low[x],low[to]);
            } else if(vis[to]) {
                low[x]=min(low[x],dfn[to]);
            }
        }
        if(low[x]==dfn[x]) {
            ++col_num;
            vis[x]=false;
            while(stack[top+1]!=x) {
                col[stack[top]]=col_num;
                value[col_num]+=Right[stack[top]];
                ans=max(ans,value[col_num]);
                vis[stack[top--]]=false;
            }
        
        }
    }
    
    void Shrink_point() {
        for(int i=1; i<=n; i++)
            for(int j=head[i]; j; j=edge[j].pre) {
                int to=edge[j].to;
                if(col[i]!=col[to])
                    add1(col[i],col[to]);
            }
    }
    
    
    bool v[M];
    void spfa(int x) {
        memset(dis,0,sizeof(dis));
        memset(v,0,sizeof(v));
        dis[x]=value[x];
        queue<int>q;
        q.push(x);
        v[x]=true;
        while(!q.empty()) {
            int fst=q.front();
            q.pop();
            v[fst]=false;
            for(int i=head1[fst]; i; i=edge1[i].pre) {
                int to=edge1[i].to;
                if(dis[to]<dis[fst]+value[to]) {
                    dis[to]=dis[fst]+value[to];
                    if(!v[to]) {
                        v[to]=true;
                        q.push(to);
                    }
                }
            }
        }
        for(int i=1; i<=col_num; i++)
            ans=max(ans,dis[i]);
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; i++)
            scanf("%d",&Right[i]);
        int u,v;
        for(int i=1; i<=m; i++) {
            scanf("%d%d",&u,&v);
            add(u,v);
        }
        for(int i=1; i<=n; i++)
            if(!dfn[i]) tarjan(i);
        Shrink_point();
        for(int i=1; i<=col_num; i++)
            spfa(i);
        printf("%d",ans);
        return 0;
    }
    缩点

    割点:如果在图G中去掉一个顶点(自然同时去掉与该顶点相关联的所有边)后,该图的连通分支数增加,则称该顶点为G的割点

    访问路径可以绘制成下图(绿边为访问未访问顶点时经过的边,红边为访问已访问节点时经过的边):

    我们把上图称为DFS搜索树(DFS tree),上图中的绿边称为树边(tree edge),红边称为回边(back edge)。通过回边可以从一个点返回到之间访问过的顶点

    首先选定一个根节点,从该根节点开始遍历整个图(使用DFS)。

    对于根节点,判断是不是割点很简单——计算其子树数量,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。

    对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组dfn[]和low[],

    dfn[u]表示顶点u第几个被(首次)访问

    low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)。对于边(u, v),如果low[v]>=dfn[u],此时u就是割点。

    但这里也出现一个问题:怎么计算low[u]。

    假设当前顶点为u,则默认low[u]=dfn[u],即最早只能回溯到自身。

    有一条边(u, v),如果v未访问过,继续DFS,DFS完之后,low[u]=min(low[u], low[v]);

    如果v访问过(且u不是v的父亲),就不需要继续DFS了,一定有dfn[v]<dfn[u],low[u]=min(low[u], dfn[v])。

    P3388 【模板】割点(割顶)

    题目背景

    割点

    题目描述

    给出一个n个点,m条边的无向图,求图的割点。

    输入输出格式

    输入格式:

    第一行输入n,m

    下面m行每行输入x,y表示x到y有一条边

    输出格式:

    第一行输出割点个数

    第二行按照节点编号从小到大输出节点,用空格隔开

    输入输出样例

    输入样例#1
    6 7
    1 2
    1 3
    1 4
    2 5
    3 5
    4 5
    5 6
    输出样例#1:
    1 
    5

    说明

    n,m均为100000

    tarjan 图不一定联通!!!

     代码:

    #include<cstdio>
    #include<iostream>
    using namespace std;
    
    const int M = 100010;
    int dfn[M],low[M];
    int dfs_num,top,vis[M],col_num,n,m;
    int head[M],num_edge,tot;
    bool cut[M];
    
    struct Edge {
        int pre,to,len;
    } edge[M*4];
    
    void add(int u,int v) {
        edge[++num_edge].pre = head[u];
        edge[num_edge].to = v;
        head[u] = num_edge;
    }
    
    void tarjan(int x,int fa) {
        tot=0;
        dfn[x]=low[x]=++dfs_num;
        for(int i=head[x]; i; i=edge[i].pre) {
            int to=edge[i].to;
            if(!dfn[to]) {
                tarjan(to,fa);
                low[x]=min(low[x],low[to]);
                if(low[to]>=dfn[x]&&x!=fa)
                    cut[x]=true;
                if(x==fa) tot++;
    
            }
            low[x]=min(low[x],dfn[to]);
        }
        if(tot>=2&&x==fa)
            cut[x]=true;
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        int x,y;
        for(int i=1; i<=m; i++) {
            scanf("%d%d",&x,&y);
            add(x,y);
            add(y,x);
        }
        for(int i=1; i<=n; i++)
            if(!dfn[i]) tarjan(i,i);
        int ans=0;
        for(int i=1; i<=n; i++)
            if(cut[i]) ans++;
        printf("%d
    ",ans);
        for(int i=1; i<=n; i++)
            if(cut[i])
                printf("%d ",i);
        return 0;
    }
    割点

    自己选的路,跪着也要走完!!!

  • 相关阅读:
    git push提交出现Everything up-to-date提示问题
    启动Dubbo项目注册Zookeeper时提示zookeeper not connected异常原理解析
    linux环境搭建mysql5.7总结
    Hadoop学习笔记:运行wordcount对文件字符串进行统计案例
    kafka3.0创建topic出现zookeeper is not a recognized option
    sql_waf绕过
    win11环境映像劫持
    vulnhub靶场—devguru
    vulhub-Presidential靶场解题过程
    php命令执行无回显判断及利用方法
  • 原文地址:https://www.cnblogs.com/wsdestdq/p/7711353.html
Copyright © 2011-2022 走看看