最小树形图
一些闲话
学完朱刘最小生成树系列就告一段落了
总的来说,最小生成树类的问题都是组合优化问题,最小斯坦纳树和最小树形图其实都是最小生成树的变种,一个是可以经过其他点串联起几个关键点,另一个是有向图上的最小生成树
言归正传,这篇博文主要是为了介绍最小树形图的
最小树形图
对于一张有向图,我们可以找出一个根节点生成一棵有向的最小生成树感性理解一下就是MST的无向边变成有向边
常用的解决办法就是朱刘算法(Edmonds算法),还有就是tarjan的DMST算法没错,还是那个tarjan,这里仅介绍朱刘算法
首先根据直觉我们贪心的选择除根结点外每个结点权值最小的入边,得到一个最短弧集合E和权值的累加和tot,但是如果这些边都选就会产生一个问题,那就是会出现环
那怎么办呢?native一点的思路就是“有环那就断环呗”
基于上述思想,我们接下来对E进行缩点,然后对缩点后的图进行重建,对于一条边(u,v)边权为w,u是环外结点,v是环上节点,本着不存在环的原则,我们加上这个环就必须断掉一条环边,所以这里我们选择将环上指向v的边(u',v)边权为w'断掉,新的边(u,t) //t是缩点后的环, 边权就是 w - w',即加上(u,v)删掉(u',v)
最后不断执行这个过程直到不存在环,将得到的图展开就得到了最小树形图
总结一下算法流程顺便说一下实现细节
- 找到最小弧集合,这里注意,如果除了根结点以外有一个点没有入度,那说明原图不连通,无解
- 对最短弧集合进行缩点,这里要判断一下,否则会一直走环,如果没有环了就直接结束
- 重新建图
- 最后把原图展开
时间复杂度是 O(VE)
实现
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define mod 1e9+7
const int MAXN = 101010;
const int inf = 1e9;
using namespace std;
struct edge{ int u, v, w; } e[MAXN];
int mi[MAXN], pre[MAXN];
int id[MAXN];
int vis[MAXN];
int n, m, root;
inline int read( ){
int x = 0 ; short w = 0 ; char ch = 0;
while( !isdigit(ch) ) { w|=ch=='-';ch=getchar();}
while( isdigit(ch) ) {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return w ? -x : x;
}
int Edmonds( ){
int ans = 0;
while( 1 ){
for( int i = 1; i <= n; i++ ) mi[i] = inf;
for( int i = 1; i <= m; i++ ){
int x = e[i].u, y = e[i].v;
if( x != y and e[i].w < mi[y]){
mi[y] = e[i].w;
pre[y] = x;
}
}
for( int i = 1; i <= n; i++ )
if( i != root and mi[i] == inf ) return -1;
int tot = 0;
for( int i = 1; i <= n; i++ ) vis[i] = id[i] = 0;
for( int i = 1; i <= n; i++ ){
if( i == root ) continue;
ans += mi[i];
int pos = i;
while( pos != root and pos != i and !id[pos] ){//
vis[pos] = i;
pos = pre[pos];
}
if( !id[pos] and pos != root ){
id[pos] = ++tot;
for( int i = pre[pos]; i != pos; i = pre[i] )
id[i] = tot;
}
}
if( !tot ) break;
for( int i = 1; i <= n; i++ )
if( !id[i] ) id[i] = ++tot;
for( int i = 1; i <= m; i++ ){
int x = e[i].u, y = e[i].v;
e[i].u = id[x]; e[i].v = id[y];
if( id[x] != id[y] ) e[i].w -= mi[y];
}
root = id[root];
n = tot;
}
return ans;
}
int main( ){
n = read( ); m = read( ); root = read( );
for( int i = 1; i <= m; i++ ){
int x = read( ), y = read( ), z = read( );
e[i] = (edge){ x, y, z };
}
cout << Edmonds( );
return 0;
}