最小树形图朱刘算法总结

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

总结及题目: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
原文地址:https://www.cnblogs.com/clno1/p/10985204.html