最小树形图:朱刘算法
一.定义
无向图我们可以找到无向图的最小生成树,而有向图可不可以找到类似的东西呢?由此我们引入了最小树形图。
我们知道一个无向图的生成树中,每个点可以相互抵达,最小生成树就是树上权值和最小的生成树。因此我们定义一个树形图为根节点可以抵达所有点的树,而树上所有边都是从原图中选取而来(感觉说的比较迷糊)。当然所有树形图中权值最小的树形图就是最小树形图啦。
对于一个最小树形图来说,我们首先要选取一个根。最小树形图可能无解,这个用一个简单的DFS就可以判断。
二.DAG的最小树形图
我们显然发现在树形图中除了根 \(root\),其他每个点都有且仅有一条入边。
所以对于DAG来说,我们对于每个点选取最小的入边即可找到最小树形图。
证明显然:对于DAG不可能出现选择了环,而无环的有 \(n-1\) 条边的图一定是一棵树,且一定是最小的树形图,这个证明与Kruskal类似。
三.环的最小树形图
此处我们就考虑一个单纯的环上找最小树形图。
显然,我们可以从 \(root\) 开始转一圈删一条边即可。
四.朱刘算法
朱刘算法(Chu–Liu/Edmonds' algorithm)是算法领域少见的由中国人提出的算法,由朱永津和刘振宏于1965年提出。
我们考虑直接应用我们在DAG上提出的算法,我们发现这个算法选出来的边可能有两种可能:要么它就是构成了一棵树,那么这就是答案;要么它构成的图不联通,即存在环。
考虑环的处理。我们显然要把一条环边换成环外的边。
看到环,我们自然而然想到缩点,把这个环缩成一点。因为我们需要向这个环上连上一边,考虑如何连边。我们显然外向边的长度一定大于环内边的长度,因为我们选择的策略是贪心的。
\(ans\) 先加上这个环的边权和,然后我们把指向环的边的边权设成自己的长度减去所连向的环内点在环中指向的点的边的长度。
我们这样做显然就可以做到删掉一条不需要的边,使其权值可以很好地被抵消。
我们缩点之后,显然成了一张新的图,我们现在按DAG的方法求最小树形图。如果没环就是答案了,如果有环就是按照上述处理方法再继续跑下去。
五.注意事项
1.时间复杂度:\(O(VE)\)。
2.要去掉重边和自环,否则时间复杂度会大大增加。
六.CODE
#include<bits/stdc++.h>
#define int long longusing namespace std;const int N=110,M=1e4+100,INF=1e18;int n,m,root;
struct EDGE{int u,v,w;
}edge[M];
int mn[N],fa[N],tp[N],lp[N];
int tot;
int ans;int zhuliu()
{while(true)//只要有环就一直跑{for(int i=1;i<=n;i++)mn[i]=INF,fa[i]=tp[i]=lp[i]=0;mn[root]=0;for(int i=1;i<=m;i++){if(edge[i].u!=edge[i].v && edge[i].w<mn[edge[i].v])mn[edge[i].v]=edge[i].w,fa[edge[i].v]=edge[i].u;}//Step1:找到最小的入边for(int i=1;i<=n;i++){ans+=mn[i];if(mn[i]==INF) return -1;}//Step2:找到"DAG"的最小树形图for(int u=1,v=1;u<=n;u++,v=u){while(v!=root && tp[v]!=u && !lp[v])tp[v]=u,v=fa[v];if(v!=root && !lp[v]){lp[v]=++tot;for(int k=fa[v];k!=v;k=fa[k]) lp[k]=tot;}}//Step3:找环if(!tot) return ans;//Step4:无环,返回答案for(int i=1;i<=n;i++)if(!lp[i])lp[i]=++tot;for(int i=1;i<=m;i++)edge[i].w-=mn[edge[i].v],edge[i].u=lp[edge[i].u],edge[i].v=lp[edge[i].v];//Step5:缩点n=tot,root=lp[root],tot=0;}
}signed main()
{cin>>n>>m>>root;for(int i=1;i<=m;i++)cin>>edge[i].u>>edge[i].v>>edge[i].w;cout<<zhuliu()<<endl;return 0;
}