朱刘算法
一个有向图,存在从某个点为根的,可以到达所有点的一个最小生成树,则它就是最小树形图。
朱刘算法用来求最小树形图,复杂度O(nm)。
大概思想就是缩点+贪心加边。
如果图中存在环,就把环缩成一个点,一直到没有环为止。
考虑正确性:
最小树形图和最小生成树的区别就在与有方向,对于一个环,我们可以选择一条入边+所有环边-1(-1是因为有一条入边可以去掉一条环边)
而对于每一个点,我们用一个minn数组存下了与它直接相连的点到它的最小边,ans等于所有minn的累加,那么一定是最优解。
用while一直执行缩点过程。
注意:vis用来避免一直走环。
具体流程和解释代码中给出(借鉴洛谷题解)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define N 103 #define M 10002 #define INF 210000001 #define LL long long using namespace std; int read() { int x=0,f=1;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } struct EDGE{ int a,b,v; }w[M]; int minn[N],id[N],vis[N],fa[N]; int cnt=0; //cnt当前图环的数量 //id[u]代表u节点在第id[u]个环中 //vis[u]打标记避免一直走环 //minn[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u LL ans=0; int zhuliu(int n,int m,int r) { while(1) { for(int i=1;i<=n;++i) minn[i]=INF,id[i]=vis[i]=0; for(int i=1;i<=m;++i) { if(w[i].a!=w[i].b&&w[i].v<minn[w[i].b])//不是自环 并且边权比选定的还小 fa[w[i].b]=w[i].a,minn[w[i].b]=w[i].v; } int u; minn[r]=0; for(int i=1;i<=n;++i) { if(minn[i]==INF)return 0;//存在一个不可以连接的点,什么不能找到最小树形图 ans+=minn[i];//这里就会更新到ans中 for(u=i;u!=r&&!id[u]&&vis[u]!=i;u=fa[u])vis[u]=i;//打上标记,这里走过的是一条链,vis打上i而不是1,因为可能多条边指向一个点 if(u!=r&&!id[u])//没有走到根,找到一个新环 { id[u]=++cnt; for(int v=fa[u];v!=u;v=fa[v])id[v]=cnt; } } if(!cnt)return 1;//没有环了,说明现在就是最小树形图,边权和在上面就已经加入ans了 for(int i=1;i<=n;++i) if(!id[i])id[i]=++cnt;//i节点不存在当前树中 就给他自己成一个环 for(int i=1;i<=m;++i) { int last=minn[w[i].b];//last等于当前连进v点的边的最小权值 if((w[i].a=id[w[i].a])!=(w[i].b=id[w[i].b]))w[i].v-=last; //缩环的时候记得加入答案,环上的出边直接接上,入边要注意,选一条入边相当于删掉一条环边 //当前边的两个端点不在同一个环内 } n=cnt;cnt=0;r=id[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环 } } int main() { int n=read(),m=read(),r=read(); for(int i=1;i<=m;++i) w[i].a=read(),w[i].b=read(),w[i].v=read(); if(zhuliu(n,m,r))printf("%lld\n",ans); else printf("-1\n"); }
再放一个没有注释的
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define N 103 #define M 10002 #define INF 210000001 #define LL long long using namespace std; int read() { int x=0,f=1;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } struct EDGE{ int a,b,v; }w[M]; int minn[N],id[N],vis[N],fa[N]; int cnt=0; LL ans=0; int zhuliu(int n,int m,int r) { while(1) { for(int i=1;i<=n;++i) minn[i]=INF,id[i]=vis[i]=0; for(int i=1;i<=m;++i) { if(w[i].a!=w[i].b&&w[i].v<minn[w[i].b]) fa[w[i].b]=w[i].a,minn[w[i].b]=w[i].v; } int u; minn[r]=0; for(int i=1;i<=n;++i) { if(minn[i]==INF)return 0; ans+=minn[i]; for(u=i;u!=r&&!id[u]&&vis[u]!=i;u=fa[u])vis[u]=i; if(u!=r&&!id[u]) { id[u]=++cnt; for(int v=fa[u];v!=u;v=fa[v])id[v]=cnt; } } if(!cnt)return 1; for(int i=1;i<=n;++i) if(!id[i])id[i]=++cnt; for(int i=1;i<=m;++i) { int last=minn[w[i].b]; if((w[i].a=id[w[i].a])!=(w[i].b=id[w[i].b]))w[i].v-=last; } n=cnt;cnt=0;r=id[r]; } } int main() { int n=read(),m=read(),r=read(); for(int i=1;i<=m;++i) w[i].a=read(),w[i].b=read(),w[i].v=read(); if(zhuliu(n,m,r))printf("%lld\n",ans); else printf("-1\n"); }