• 朱刘算法学习笔记


    提出问题

    首先给出树形图的定义:(可以近似理解为有向图上的生成树)(定义取自训练指南)

    • 有向图中定义
    • 无环
    • 根节点可以到达任意一个节点
    • 根节点入度为 0 ,其他节点入度为 1

    然后是最小树形图:

    • 边权和最小的树形图。

    分析问题

    算法简介

    这个算法名叫 朱-刘算法,根据网上说法是 朱永津-刘振宏 发明的,

    1965年,提出最小树形图算法,运用图的收缩与扩张的运算,绘出了在一个有向图中求最小树形图的一个多项式算法,在拟阵交计算上为首创,被称为“朱-刘算法”。

    流程

    Warning: 以下说明的是 树有根 的情况。

    首先放一张 luogu题解 的图:(第二张看上去并不友好,于是拿了第一张)

    luogu题解图片

    • 首先,每个点的出度可能会有多个,不好考虑,所以按照入度为 (1) (非根)这个性质来思考。
    • 容易想到,每次对除根外每个点找出权值最小的入边并累计入答案中。
    • 判断选出的边是否存在环,如果没有就说明找到了最小树形图,退出。
    • 将所有环缩点,构造一个新图,对于原图的每条边:如果这条边在环内,删去;否则,如果该边的终点(指向节点)在环内,将权值修改为(这条边原先的权值-终点在环上的入边权值)
    • 重复这个步骤,直到满足无环为止。

    正确性证明

    • 其实朱刘算法本质是一个反悔贪心
    • 对于每个环,显然一定存在一个最优解,只去掉一条边(如果选了两条,把其中一条选回去,答案不会变差)
    • 如果你选了新的权值(就是作差过的),相当于去掉环上对应的入边,然后改选了当前这一条。程序里不需要判终点是否在环内,直接把不在环上的点当做一个环处理即可。因为这样修改边权,减去的权值就是原来的边权,就和“选这一条边”的意义是一样的了。
    • 每次缩点点数至少会减一,复杂度 (O(VE))

    拓展——不定根

    这个其实和 多源最短路 之类的解决方法是类似的,考虑对每个点都连到一个虚根 (rt)(n) 条边均由 (rt) 指向其他点,并且把边权设置为 (原来的所有边权和 (sum) +1) 。然后就可以跑有根的朱刘了。

    如果最后跑出来,权值和 (>2 imes sum) 说明用了两条新的边,但是原图的树形图里面显然不可能存在两个根节点,所以原图是无法形成最小树形图的。

    否则就可以根据唯一的一条新加边指向的点确定树形图的根节点,因为它除了 (rt) 以外,没有被原图中任何其他节点指向。

    解决问题

    代码来源:P4716 【模板】最小树形图

    如果你需要通过代码更好地理解算法,那么这里提供:

    代码变量名称约定
     n,m,rt:题目给出的点数,边数,根节点
     min_pre[],fa[]:每次执行中找到的最小入边的权值,入边的起点
     cnt_cyc,incyc_id[]:环的编号计数,每个点在哪个环里面
     f[]:类似并查集中的最高祖先,找一个点沿着入边往上跳的最终节点
    

    代码实现:

    //Author: RingweEH
    const int N=110,M=1e4+10,inf=0x3f3f3f3f;
    struct edge
    {
        int u,v; ll val;
    }e[M];
    int n,m,rt,cnt_cyc,fa[N],incyc_id[N],f[N],min_pre[N];
    ll ans=0;
    
    int ZhuLiu()
    {
        while ( 1 )
        {
            cnt_cyc=0;
            for ( int i=1; i<=n; i++ )
                incyc_id[i]=f[i]=0,min_pre[i]=inf;
            //---------------------init----------------------
            for ( int i=1; i<=m; i++ )
                if ( e[i].u!=e[i].v && e[i].val<min_pre[e[i].v] )
                    fa[e[i].v]=e[i].u,min_pre[e[i].v]=e[i].val;
            //--------------找每个点的最小入边---------------
            int now=min_pre[rt]=0;
            for ( int i=1; i<=n; i++ )
            {
                if ( min_pre[i]==inf ) return 0;    //孤立点特判
                ans+=min_pre[i];        //不管如何先把边权加进去就好了
                for ( now=i; now!=rt && f[now]!=i && !incyc_id[now]; now=fa[now] )
                    f[now]=i;   //从i不断往选定的入边跳,途中不能往其他已经判定的环里面跳
                if ( now!=rt && !incyc_id[now] )    
                //看上面循环的判断条件,只满足了 f[now]==i ,也就是形成了环
                {
                    incyc_id[now]=++cnt_cyc;
                    for ( int v=fa[now]; v!=now; v=fa[v] )
                        incyc_id[v]=cnt_cyc;
                }
            }
            if ( !cnt_cyc ) return 1;
            //-----------------------找环----------------------
            for ( int i=1; i<=n; i++ )  //给不在环中的点也赋一个标号,方便判断
                if ( !incyc_id[i] ) incyc_id[i]=++cnt_cyc; 
            for ( int i=1; i<=m; i++ )
            {
                int las=min_pre[e[i].v];    //e[i].v的最小入边权
                e[i].u=incyc_id[e[i].u]; e[i].v=incyc_id[e[i].v];   //缩成同一个点,也就是环编号
                if ( e[i].u!=e[i].v )  e[i].val-=las;   //如果不在同一个环里面就修改边权
            }
            n=cnt_cyc; rt=incyc_id[rt]; //缩点完成后的点数就是环的个数,并更新根节点编号。
        }
    }
    
    int main()
    {
        n=read(); m=read(); rt=read();
        for ( int i=1; i<=m; i++ )
            e[i]=(edge){read(),read(),read()};
        
        if ( ZhuLiu() ) printf( "%lld",ans );
        else printf( "-1
    " );
        return 0;
    }
    

    习题

    UVA11865 Stream My Contest

    题意:你需要花费不超过 (cost) 元来搭建一个比赛网络。网络中有 (n) 台机器,编号 (0sim n-1) ,0 为服务机,其他均为客户机。一共有 (m) 条可以使用的网线,数据只能从 (u_i o v_i) 单向传递,带宽 (b_i) Kbps,费用 (c_i) 元。每台客户机应当恰好从一台机器接受数据,服务器不接受数据。最大化最小带宽。

    思路:如果要最大化最小带宽,很容易想到二分最小带宽并去掉所有小于带宽的边。而让所有客户机都能收到,其实就是服务机要能到达每个客户机,要是对性质熟悉的话就很容易想到树形图。那么对于二分的判定,只需要求出从 0 出发的最小树形图,判断权值和是否超过给定 (cost) 即可。

    //Author: RingweEH
    int ZhuLiu()
    {
        ans=0;
        while ( 1 )
        {
            cnt_cyc=0;
            for ( int i=1; i<=n; i++ )
                incyc_id[i]=f[i]=fa[i]=0,min_pre[i]=inf;
                
            for ( int i=1; i<=newm; i++ )
                if ( e[i].u!=e[i].v && e[i].val<min_pre[e[i].v] )
                    fa[e[i].v]=e[i].u,min_pre[e[i].v]=e[i].val;
                    
            int now=min_pre[rt]=0;
            for ( int i=1; i<=n; i++ )
            {
                if ( min_pre[i]==inf ) return -1;
                ans+=min_pre[i];
                for ( now=i; now!=rt && f[now]!=i && !incyc_id[now]; now=fa[now] )
                    f[now]=i;   
                if ( now!=rt && !incyc_id[now] )    
                {
                    incyc_id[now]=++cnt_cyc;
                    for ( int v=fa[now]; v!=now; v=fa[v] )
                        incyc_id[v]=cnt_cyc;
                }
            }
            if ( !cnt_cyc ) break;
    
            for ( int i=1; i<=n; i++ )  
                if ( !incyc_id[i] ) incyc_id[i]=++cnt_cyc; 
            for ( int i=1; i<=newm; i++ )
            {
                int las=min_pre[e[i].v];    
                e[i].u=incyc_id[e[i].u]; e[i].v=incyc_id[e[i].v];  
                if ( e[i].u!=e[i].v )  e[i].val-=las;  
            }
            n=cnt_cyc; rt=incyc_id[rt]; 
        }
        return ans;
    }
    
    bool check( int x )
    {
        rt=1; n=savn; newm=0;
        for ( int i=1; i<=m; i++ )
            if ( save[i].wide>=x ) e[++newm]=save[i];
        int answer=ZhuLiu();
        return answer!=-1 && answer<=cost;
    }
    
    int main()
    {
        int T=read(); n=-1;
        for ( int cas=1;cas<=T; cas++)
        {
            n=savn=read(); m=read(); cost=read(); int mxwid=0;
            for ( int i=1; i<=m; i++ )
            {
                e[i].u=read()+1,e[i].v=read()+1; e[i].wide=read(); e[i].val=read();
                mxwid=max( mxwid,e[i].wide ); save[i]=e[i];
            }
            
            int l=0,r=mxwid,res=-1;
            while ( l<=r )
            {
                int mid=(l+r)>>1; 
                if ( check(mid) ) l=mid+1,res=mid;
                else r=mid-1;
            }
            if ( res==-1 ) { printf( "streaming not possible.
    " ); continue; }
            printf( "%d kbps
    ",res );
        }
    }
    

    参考

    墨染空大佬的博客

  • 相关阅读:
    士兵杀死(两)(南阳116)
    Android 墙纸设置代码 详细说明
    Laravel nginx 伪静态规则
    STL源代码分析——STL算法merge合并算法
    第29周六
    第29周五
    第29周四
    第29周三
    2014第29周二
    第29周一
  • 原文地址:https://www.cnblogs.com/UntitledCpp/p/14012523.html
Copyright © 2020-2023  润新知