zoukankan      html  css  js  c++  java
  • 浅谈Tarjan缩点(分析+模板)

    昨天一看发现我的博客数量到100篇了,撒花✿✿ヽ(°▽°)ノ✿


    根据标题我们也知道,想要在接下来的十分钟不浪费生命

    读者需要先行学习Tarjan强联通分量

    如果不会的话可以点击这里:https://www.cnblogs.com/WWHHTT/p/9744658.html

    好的,现在我们就假设大家都知道了Tarjan,来看看如何利用Tarjan缩点

    在学习如何写代码之前,我们必须要先弄明白什么是缩点

    也就是定义

    百度百科上并没有给出详细解释,我就来总结几句

    缩点就是把一个有向图中的强联通分量转换成一个点

    转换完之后的图是一个DAG(有向无环图)

    通常用来解决图中具有传递性的问题(还有最大值和乘法原理)

    我们来举个例子吧

    上面这个图中,圆里的是编号,红色的是点的权值,箭头代表边的方向

    这个图缩点之后是这样的

    符号所表示的东西不变,原来的2,3,4变成了现在的2,原来的5是现在的3

    是不是感觉没有多难,下面我来带大家看一下具体的实现流程

    分为两步:

    1.求出图中的强联同分量,并转化为点

    2.将转化出来的新的点建成一个新的图

    step1:

    如何求出强联通分量就不多说了(不会的去看上面的链接)

    每一种强联通分量会缩成一个点,这个店的编号我们就定为发现它的顺序,也就是染上的颜色

    每一种颜色(一个强联通分量)对应一个点

    但是要在中间加一个操作,用来计算一个联通分量的所缩成的点的点权(看注释)

    void Tarjan(int x,int fa){//Tarjan模板 
        low[x]=dfn[x]=++tot;
        sta[++size]=x;
        book[x]=1;
        for(int e=head1[x];e;e=nxt1[e]){
            if(!dfn[to1[e]]){
                Tarjan(to1[e],x);
                low[x]=min(low[x],low[to1[e]]);
            }
            else if(book[to1[e]]) low[x]=min(low[x],dfn[to1[e]]);
        }
        if(dfn[x]==low[x]){
            color[x]=++color_cnt;
            v2[color_cnt]=v[x];
            book[x]=0;
            while(sta[size]!=x){
                color[sta[size]]=color_cnt;
                book[sta[size]]=0;
                v2[color_cnt]+=v[sta[size]];//这个点的权值的计算,根据题目来确定 
                size--;
            }
            size--;
        }
        return ;
    }

    step2:
    建图的边怎么练一直是个难点,首先可以确定的是因为原来的强连通分量已经变成一个个的点了

    所以肯定无法照搬原图

    但也不可能自创,所以需要判断,从原来的图上选择那些边

    选边的原则是不能是一个强连通分量里的边

    也就是说别的边都可以选

    那么有的人就问了,缩完点之后不担心友谊重边吗

    这很好处理,用邻接矩阵来存(不推荐),或者在之后的程序中标记都可以

    特别提到,印的图要用一个新的邻接表来存

    而且要连接的不是原来的点,而是两个点被染成的颜色

    代码实现的话很简单,循环和dfs都可以,这里我们采用循环(代码量较小)

    void change(){
        for(int i=1;i<=n;i++){
            for(int e=head1[i];e;e=nxt1[e]){
                if(color[i]!=color[to1[e]]) add2(color[i],color[to1[e]]);
            }
        }
        return ;
    }

    那么最后我们来总结一下代码

    先给一组数据:(对应上面的图)
    5 5   //点的数量  边的数量

    1 1 1 1 1   //点权

    1 2  //边

    2 3

    3 4

    4 2

    2 5

    输出:

    3 //新的点的数量

    1 3 1   //新的点的权值

    下面给出代码:

    #include<iostream>
    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    inline int min(int a,int b){return a<b?a:b;}
    inline int max(int a,int b){return a>b?a:b;}
    inline int rd(){
        int x=0,f=1;
        char ch=getchar();
        for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
        for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
        return x*f;
    }
    inline void write(int x){
         if(x<0) putchar('-'),x=-x;
         if(x>9) write(x/10);
         putchar(x%10+'0');
         return ;
    }
    int n,m;
    int v[100006];
    int head1[100006],nxt1[200006],to1[200006];
    int total1=0;
    void add1(int x,int y){
        total1++;
        to1[total1]=y;
        nxt1[total1]=head1[x];
        head1[x]=total1;
        return ;
    }
    int dfn[100006],low[100006];
    int color[100006];
    int tot=0;
    int color_cnt=0;
    int book[100006];
    int sta[100006];
    int v2[100006];
    int size=0;
    void Tarjan(int x,int fa){//Tarjan模板 
        low[x]=dfn[x]=++tot;
        sta[++size]=x;
        book[x]=1;
        for(int e=head1[x];e;e=nxt1[e]){
            if(!dfn[to1[e]]){
                Tarjan(to1[e],x);
                low[x]=min(low[x],low[to1[e]]);
            }
            else if(book[to1[e]]) low[x]=min(low[x],dfn[to1[e]]);
        }
        if(dfn[x]==low[x]){
            color[x]=++color_cnt;
            v2[color_cnt]=v[x];
            book[x]=0;
            while(sta[size]!=x){
                color[sta[size]]=color_cnt;
                book[sta[size]]=0;
                v2[color_cnt]+=v[sta[size]];//这个点的权值的计算,根据题目来确定 
                size--;
            }
            size--;
        }
        return ;
    }
    int head2[100006],nxt2[200006],to2[200006];
    int total2=0;
    void add2(int x,int y){
        total2++;
        to2[total2]=y;
        nxt2[total2]=head2[x];
        head2[x]=total2;
        return ;
    }
    void change(){
        for(int i=1;i<=n;i++){
            for(int e=head1[i];e;e=nxt1[e]){
                if(color[i]!=color[to1[e]]){
                    add2(color[i],color[to1[e]]);//将两个点连边 
                }
            }
        }
        return ;
    }int main(){
        n=rd(),m=rd();
        for(int i=1;i<=n;i++) v[i]=rd();//得到点权 
        for(int i=1;i<=m;i++){
            int x=rd(),y=rd();
            add1(x,y);//有向边 
        }
        for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i,0);
        change();
        int ans=0;
        printf("%d
    ",color_cnt);
        for(int i=1;i<=color_cnt;i++){
            printf("%d ",v2[i]);
        }
        return 0;
    }
    蒟蒻总是更懂你✿✿ヽ(°▽°)ノ✿
  • 相关阅读:
    激活函数
    C++ 三大属性之多态
    C++ 编译运行过程
    优化方法
    目标检测中的IOU
    pytorch 单机多gpu运行
    WSAEventSelect网络模型
    根据数组中的索引删除对应的值
    从以下几点提高服务器并发量
    std::function
  • 原文地址:https://www.cnblogs.com/WWHHTT/p/9825766.html
Copyright © 2011-2022 走看看