最小树形图: 类似最小生成树。给定有向图,求用给定边所能构成的最小树。
朱刘算法: 贪心算法。可以想到每次都找每个点的入边中最小的一个来构成树,如果构成了,就是最小的。但是构成过程中可能会出现环,这时候就需要缩点。
而且因为每个点只选取一条入边,所以构成的环一定是简单环,没有必要用tarjan求强连通分量来找缩点。这样编写难度会小很多。
每次找到环以后需要更新权值,规则是这样的:对于每条指向环的边,该边边权减去所指向点的最小入边。更新后继续找环,直到没有环,即找到最小树形图。
时间复杂度:O(nm)
对于无定根的图: 设置一个超级源点,该源点向每个点连一条sum+1长度的边,sum为所有边的权值和,最后如果结果ans大于sum+(sum+1),则无解。否则解就为ans-(sum+1)。
例题:最小树形图 (有定根)
#include<bits/stdc++.h>
#define MAXN 105
#define MAXM 10005
#define INF 1000000000
#define ll long long
using namespace std;
struct TO
{
long long id,v;
};
struct NODE
{
ll scc;//属于哪个缩点
ll mId,mv,top;//minEdgeId,minEdgeV,记录最小入边的id和权值
vector<TO>to;
};NODE a[MAXN];
struct EDGE
{
ll u,v,w;
};EDGE b[MAXM];
long long n,m,r,u,v,w,cnt,tot=0;
ll zhu_liu()
{
while(1)
{
for(ll i=1;i<=n;i++){a[i].scc=a[i].top=0;a[i].mv=INF;}
a[r].mv=cnt=0;
for(ll i=1;i<=m;i++)if(b[i].u!=b[i].v&&b[i].w<a[b[i].v].mv){//找最小边
a[b[i].v].mv=b[i].w;a[b[i].v].mId=b[i].u;
}
for(ll i=1;i<=n;i++)if(a[i].mv==INF)return 0;//无法生成树
for(ll i=1;i<=n;i++){
tot+=a[i].mv;
ll x=i;
for(;x!=r&&a[x].top!=i&&a[x].scc==0;x=a[x].mId)a[x].top=i;
if(x!=r&&a[x].scc==0){
a[x].scc=++cnt;
for(ll j=a[x].mId;j!=x;j=a[j].mId)a[j].scc=cnt;
}
}
if(cnt==0)return 1;//找到最小树
for(ll i=1;i<=n;i++)if(a[i].scc==0)a[i].scc=++cnt;
for(ll i=1;i<=m;i++){
ll u=b[i].u,v=b[i].v;
b[i].u=a[u].scc;b[i].v=a[v].scc;
if(a[u].scc!=a[v].scc)b[i].w-=a[v].mv;
}
n=cnt;r=a[r].scc;
}
return 0;
}
int main()
{
cin>>n>>m>>r;
for(long long i=1;i<=m;i++){
cin>>u>>v>>w;
a[u].to.push_back({v,w});//在例题里没什么用
b[i]=(EDGE){u,v,w};
}
ll jug=zhu_liu();
if(jug==0)cout<<-1;
else cout<<tot;
return 0;
}