• 最小树形图朱刘算法总结


    在有向图上的最小生成树就是最小树形图啦,最小树形图用朱-刘算法解决。

    总结及题目:https://blog.csdn.net/y_marcel_h/article/details/80459821

    模板来自:https://blog.csdn.net/qq_27437781/article/details/70671244

    原理上面大佬说得很清楚了:大题上完整的朱、刘算法就是由四个大步骤组成的:

    1、求最短弧集合E

    2、判断集合E中有没有有向环,如果有转步骤3,否则转4

    3、收缩点,把有向环收缩成一个点,并且对图重新构建,包括边权值的改变和点的处理,之后再转步骤1。

    4、展开收缩点,求得最小树形图。

    HDU-3164

    有向图给定根的最小树形图模板题,套用上面大佬的模板。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #define INF 0x3f3f3f3f
    #define MAX 110 
    using namespace std;
    int n, m;
    struct Edge { int u,v; double w; } edge[MAX*MAX];
    double x[MAX],y[MAX],in[MAX];
    int st,pre[MAX],id[MAX],vis[MAX];
    
    double ZL(int root, int nodenum, int edgenum) {
        double ret = 0;
        while(true) {
            ///找最小入边
            for(int i = 0; i < nodenum; i++) in[i] = INF;  ///初始化入边INF 
            for(int i = 0; i < edgenum; i++) { ///遍历每条边
                int u = edge[i].u;
                int v = edge[i].v;
                if(edge[i].w < in[v]&&u != v) {
                    pre[v] = u;
                    in[v] = edge[i].w;
                    if(root == u) st = i; ///如果一个点是由超级节点到达的 那这个点就是原图中的起点            
                }
            }
            ///由于超级节点到各点的权值为sum 是小于INF, 那么图中所有点的入度都不可能为INF 所以下面这个for循环可要可不要
            for(int i = 0; i < nodenum; i++) { ///判断是否有最小树形图
                if(i == root) continue;
                if(in[i]==INF) return -1;  //无解 
            }
            ///找环
            int cnt = 0;
            memset(id, -1, sizeof(id));  //新图结点编号 
            memset(vis, -1, sizeof(vis));  //找环 
            in[root] = 0;
            for(int i = 0; i < nodenum; i++) {
                ret += in[i];
                int v = i;
                while(vis[v] != i&&id[v] == -1&&v != root) {  ///每个点寻找其前序点,要么最终寻找至根部,要么找到一个环 
                    vis[v] = i;
                    v = pre[v];
                }
                if(v != root&&id[v] == -1) {  ///有环 缩点 把同处于一个环中的各个点都标为人工节点cnt 
                    for(int u=pre[v];u!=v;u=pre[u]) id[u] = cnt;
                    id[v]=cnt++;
                }
            }
            if(cnt==0) break; ///无环 找到最小树形图 则break
            ///建新图
            for(int i = 0; i < nodenum; i++) 
                if(id[i]==-1) id[i] = cnt++;
            for(int i = 0; i < edgenum; i++) {
                int u = edge[i].u,v = edge[i].v;
                edge[i].u = id[u];
                edge[i].v = id[v];
                if(id[u] != id[v]) edge[i].w -= in[v];
            }
            nodenum = cnt;
            root = id[root];
        }
        return ret;
    }
    
    int main()
    {
        while(~scanf("%d%d",&n, &m)) {
            for (int i=0;i<n;i++) scanf("%lf%lf",&x[i],&y[i]);
            for (int i=0;i<m;i++) {
                scanf("%d%d",&edge[i].u,&edge[i].v);
                edge[i].u--; edge[i].v--;
                edge[i].w=(x[edge[i].u]-x[edge[i].v])*(x[edge[i].u]-x[edge[i].v])+
                          (y[edge[i].u]-y[edge[i].v])*(y[edge[i].u]-y[edge[i].v]);
                edge[i].w=sqrt(edge[i].w);
            }
            double ans=ZL(0,n,m);  //从0开始计数
            if (ans==-1) puts("poor snoopy"); else printf("%.2f
    ",ans); 
        }
        return 0;
    }
    View Code

    HDU-2121

    有向图不定根的最小树形图模板题,要求最小树形图大小及最小树形图条件下根编号最小。继续套用上面大佬模板。

    #include <bits/stdc++.h>
    #define INF 0x3f3f3f3f
    #define MAX 1010 
    using namespace std;
    int n, m;
    struct Edge { int u,v,w; } edge[MAX*MAX];
    int in[MAX],st;
    int pre[MAX],id[MAX],vis[MAX];
    
    int ZL(int root, int nodenum, int edgenum) {
        int ret = 0;
        while(true) {
            ///找最小入边
            for(int i = 0; i < nodenum; i++) in[i] = INF;  ///初始化入边INF 
            for(int i = 0; i < edgenum; i++) { ///遍历每条边
                int u = edge[i].u;
                int v = edge[i].v;
                if(edge[i].w < in[v]&&u != v) {
                    pre[v] = u;
                    in[v] = edge[i].w;
                    if(root == u) st = i; ///如果一个点是由超级节点到达的 那这个点就是原图中的起点            
                }
            }
            ///由于超级节点到各点的权值为sum 是小于INF, 那么图中所有点的入度都不可能为INF 所以下面这个for循环可要可不要
            for(int i = 0; i < nodenum; i++) { ///判断是否有最小树形图
                if(i == root) continue;
                if(in[i]==INF) return -1;  //无解 
            }
            ///找环
            int cnt = 0;
            memset(id, -1, sizeof(id));  //新图结点编号 
            memset(vis, -1, sizeof(vis));  //找环 
            in[root] = 0;
            for(int i = 0; i < nodenum; i++) {
                ret += in[i];
                int v = i;
                while(vis[v] != i&&id[v] == -1&&v != root) {  ///每个点寻找其前序点,要么最终寻找至根部,要么找到一个环 
                    vis[v] = i;
                    v = pre[v];
                }
                if(v != root&&id[v] == -1) {  ///有环 缩点 把同处于一个环中的各个点都标为人工节点cnt 
                    for(int u=pre[v];u!=v;u=pre[u]) id[u] = cnt;
                    id[v]=cnt++;
                }
            }
            if(cnt==0) break; ///无环 找到最小树形图 则break
            ///建新图
            for(int i = 0; i < nodenum; i++) 
                if(id[i]==-1) id[i] = cnt++;
            for(int i = 0; i < edgenum; i++) {
                int u = edge[i].u,v = edge[i].v;
                edge[i].u = id[u];
                edge[i].v = id[v];
                if(id[u] != id[v]) edge[i].w -= in[v];
            }
            nodenum = cnt;
            root = id[root];
        }
        return ret;
    }
    
    int main()
    {
        while(~scanf("%d%d",&n, &m)) {
            int sum = 0;
            for(int i=0; i<m;i++) {
                scanf("%d%d%d",&edge[i].u, &edge[i].v, &edge[i].w);
                edge[i].u++; edge[i].v++;
                sum+=edge[i].w;
            }
            sum++;
            ///在加入超级节点后  变为n+1个节点  由于超级节点到每个点都有边,也就是n条边 那么总边数就变为m + n
            ///前m条边是原图中的边,后面n-1个边是超级节点到原图中各个点的权值
            for(int i=m;i<m+n;i++) {
                edge[i].u=0;
                edge[i].v=i-m+1;
                edge[i].w=sum;
            }
            int ans = ZL(0,n+1,m+n);
            ///n+1为总结点数,m+n为总边数  
            ///ans代表以超级节点0为根的最小树形图的总权值,  
            ///将ans减去sum,如果差值小于sum,说明节点0的出度只有1,说明原图是连通图  
            ///如果差值>=sum,那么说明节点0的出度不止为1,说明原图不是连通图  
            if(ans==-1 || ans-sum>=sum) printf("impossible
    
    ");
            else printf("%d %d
    
    ",ans-sum,st-m);
        }
        return 0;
    }
    View Code

    HDU-4009

    求有向图最小树形森林,我们造一个超级点连向所有点,然后跑朱刘算法,把超级根去掉就是最小树形森林。在这一题因为独立是需要代价的,所以刚好把超级点连向原图点的长度为独立代价即可。

    #include <bits/stdc++.h>
    #define MAX 1010 
    using namespace std;
    typedef long long LL;
    const LL INF=1LL<<60;
    int n, m;
    struct Edge { LL u,v,w; } edge[MAX*MAX];
    LL X,Y,Z,in[MAX],x[MAX],y[MAX],z[MAX],st;
    LL pre[MAX],id[MAX],vis[MAX];
    
    LL ZL(LL root, LL nodenum, LL edgenum) {
        LL ret = 0;
        while(true) {
            ///找最小入边
            for(LL i = 0; i < nodenum; i++) in[i] = INF;  ///初始化入边INF 
            for(LL i = 0; i < edgenum; i++) { ///遍历每条边
                LL u = edge[i].u;
                LL v = edge[i].v;
                if(edge[i].w < in[v]&&u != v) {
                    pre[v] = u;
                    in[v] = edge[i].w;
                    if(root == u) st = i; ///如果一个点是由超级节点到达的 那这个点就是原图中的起点            
                }
            }
            ///由于超级节点到各点的权值为sum 是小于INF, 那么图中所有点的入度都不可能为INF 所以下面这个for循环可要可不要
            for(LL i = 0; i < nodenum; i++) { ///判断是否有最小树形图
                if(i == root) continue;
                if(in[i]==INF) return -1;  //无解 
            }
            ///找环
            LL cnt = 0;
            memset(id, -1, sizeof(id));  //新图结点编号 
            memset(vis, -1, sizeof(vis));  //找环 
            in[root] = 0;
            for(LL i = 0; i < nodenum; i++) {
                ret += in[i];
                LL v = i;
                while(vis[v] != i&&id[v] == -1&&v != root) {  ///每个点寻找其前序点,要么最终寻找至根部,要么找到一个环 
                    vis[v] = i;
                    v = pre[v];
                }
                if(v != root&&id[v] == -1) {  ///有环 缩点 把同处于一个环中的各个点都标为人工节点cnt 
                    for(LL u=pre[v];u!=v;u=pre[u]) id[u] = cnt;
                    id[v]=cnt++;
                }
            }
            if(cnt==0) break; ///无环 找到最小树形图 则break
            ///建新图
            for(LL i = 0; i < nodenum; i++) 
                if(id[i]==-1) id[i] = cnt++;
            for(LL i = 0; i < edgenum; i++) {
                LL u = edge[i].u,v = edge[i].v;
                edge[i].u = id[u];
                edge[i].v = id[v];
                if(id[u] != id[v]) edge[i].w -= in[v];
            }
            nodenum = cnt;
            root = id[root];
        }
        return ret;
    }
    
    int main()
    {
        while (scanf("%d%lld%lld%lld",&n,&X,&Y,&Z)==4 && n) {
            for (int i=1;i<=n;i++) scanf("%lld%lld%lld",&x[i],&y[i],&z[i]);
            m=-1;
            for (int i=1;i<=n;i++) {
                m++; edge[m].u=0; edge[m].v=i; edge[m].w=X*(z[i]);
                int t; scanf("%d",&t);
                for (int j=1;j<=t;j++) {
                    int tp; scanf("%d",&tp);
                    m++; edge[m].u=i; edge[m].v=tp;
                    edge[m].w=Y*(abs(x[i]-x[tp])+abs(y[i]-y[tp])+abs(z[i]-z[tp]));
                    if (z[i]<z[tp]) edge[m].w+=Z;
                }
            }
            LL ans=ZL(0,n+1,m+1);
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code

    HDU-4966

    给出n个课程及其要求等级a[i],有m个辅导班,每个辅导版描述k1,l1,k2,l2,v为要求k1课程达到l1才能学习,学习后课程k2能达到l2,花费v。要求算出最少花费多少钱能把每门课都修到满级a[i]。

    注意到每个辅导班其实就是一个有向边,所以我们考虑最小树形图。讲一下我的建图方法,对每个课程每个等级都建立一个点以及建立一个超级起点0,0向每个课程等级0连长度为0的边表示初始等级,然后每个辅导班就对应连边长度为花费,接着每个课程等级高向等级低连长度为0的边表示这个图要连通成为树形图:但是一旦修了等级高的,那么等级低的通过这些0边也能达到了。最后怎么表示不可能输出-1呢?起点0向每个课程最高等级连边长度为2000*1000+1(这是所有课程花费总和+1),这样就能保证出来的图是联通的啦,且一旦发现ans>=2000*1000那就是impossible啦。

    #include <bits/stdc++.h>
    #define INF 0x3f3f3f3f
    #define MAX 1010 
    using namespace std;
    int n, m,num,a[MAX],s[MAX];
    struct Edge { int u,v,w; } edge[MAX*MAX];
    int in[MAX],st;
    int pre[MAX],id[MAX],vis[MAX];
    
    void add_edge(int x,int y,int z) {
        ++num; edge[num].u=x; edge[num].v=y; edge[num].w=z; 
    }
    
    int ZL(int root, int nodenum, int edgenum) {
        int ret = 0;
        while(true) {
            ///找最小入边
            for(int i = 0; i < nodenum; i++) in[i] = INF;  ///初始化入边INF 
            for(int i = 0; i < edgenum; i++) { ///遍历每条边
                int u = edge[i].u;
                int v = edge[i].v;
                if(edge[i].w < in[v]&&u != v) {
                    pre[v] = u;
                    in[v] = edge[i].w;
                    if(root == u) st = i; ///如果一个点是由超级节点到达的 那这个点就是原图中的起点            
                }
            }
            ///由于超级节点到各点的权值为sum 是小于INF, 那么图中所有点的入度都不可能为INF 所以下面这个for循环可要可不要
            for(int i = 0; i < nodenum; i++) { ///判断是否有最小树形图
                if(i == root) continue;
                if(in[i]==INF) return -1;  //无解 
            }
            ///找环
            int cnt = 0;
            memset(id, -1, sizeof(id));  //新图结点编号 
            memset(vis, -1, sizeof(vis));  //找环 
            in[root] = 0;
            for(int i = 0; i < nodenum; i++) {
                ret += in[i];
                int v = i;
                while(vis[v] != i&&id[v] == -1&&v != root) {  ///每个点寻找其前序点,要么最终寻找至根部,要么找到一个环 
                    vis[v] = i;
                    v = pre[v];
                }
                if(v != root&&id[v] == -1) {  ///有环 缩点 把同处于一个环中的各个点都标为人工节点cnt 
                    for(int u=pre[v];u!=v;u=pre[u]) id[u] = cnt;
                    id[v]=cnt++;
                }
            }
            if(cnt==0) break; ///无环 找到最小树形图 则break
            ///建新图
            for(int i = 0; i < nodenum; i++) 
                if(id[i]==-1) id[i] = cnt++;
            for(int i = 0; i < edgenum; i++) {
                int u = edge[i].u,v = edge[i].v;
                edge[i].u = id[u];
                edge[i].v = id[v];
                if(id[u] != id[v]) edge[i].w -= in[v];
            }
            nodenum = cnt;
            root = id[root];
        }
        return ret;
    }
    
    int gid(int k,int l) { return s[k-1]+(l+1); }
    
    int main()
    {
        int Max=2000*1000+1;
        while (scanf("%d%d",&n,&m) && n) {
            for (int i=1;i<=n;i++) scanf("%d",&a[i]);
            for (int i=1;i<=n;i++) s[i]=(a[i]+1)+s[i-1];
            
            num=-1;
            for (int i=1;i<=n;i++) {
                add_edge(0,gid(i,0),0);
                add_edge(0,gid(i,a[i]),Max);
                for (int j=a[i];j;j--) add_edge(gid(i,j),gid(i,j-1),0);
            }
            for (int i=1;i<=m;i++) {
                int k1,l1,k2,l2,v; scanf("%d%d%d%d%d",&k1,&l1,&k2,&l2,&v);
                add_edge(gid(k1,l1),gid(k2,l2),v);
            }
            
            int ans=ZL(0,s[n]+1,num+1);
            if (ans>=Max) puts("-1"); else printf("%d
    ",ans);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    python之九九乘法表
    python之生成随机密码
    selenium以及浏览器驱动下载安装
    Monkey命令
    Android SDK Manager仅有一个版本的问题
    截图工具无法使用解决方法
    jmeter服务器监控插件指标简单说明
    mysql字符集小结
    mysql创建新的用户及flush privileges解析
    MySql添加远程超级管理员用户
  • 原文地址:https://www.cnblogs.com/clno1/p/10985204.html
Copyright © 2020-2023  润新知