在有向图上的最小生成树就是最小树形图啦,最小树形图用朱-刘算法解决。
总结及题目: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; }
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; }
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; }
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; }