算法步骤:
1、建立最短边集(有向边), 注意除去自环(自己连向自己)
in[v] 记录最小权值,pre[v] 记录边的起点
例子:
边1 A->B 权:5
边2 C->B 权:3
则 in[B]=3 pre[B]=C
若集合建立完毕后,仍有孤立的点,则不存在最小树形图。
2、查找有向环,并把它缩成一个点
方法: 设置环中点的编号 id[x]=newnode;
3、若没有 有向环则已经构建最小树形图 ,输出结果
否则,把剩余不在环中的点收集起来。
再更新边
int v=Edge.to;
Edge.from=id[Edge.from]
Edge.to=id[Edge.to]
重要:
如果不构成自环,即Edge.from!=Edge.to
则 Edge.cost-=in[v]
模板:
// 假设点集1-n 边集0- m-1 起点是1
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=200;
const int maxm=1e4+10;
const int INF=0x3f3f3f3f;
struct Edge
{
int from,to,cost;
Edge() {}
Edge(int f,int t,int c):from(f),to(t),cost(c) {}
}edges[maxm]; int pre[maxn];
int n,m;
int in[maxn],id[maxn],vis[maxn]; //vis记录环
void init() //建图
{
scanf("%d%d",&n,&m); int cnt=0;
for (int i=0;i<m;i++)
{
int u,v,c;
scanf("%d%d%d",&u,&v,&c);
if (u!=v) edges[cnt++]=Edge(u,v,c); //除去自环
} m=cnt;
}
int work(int s) //最小树形图算法
{
int ret=0;
while(1)
{
for (int i=1;i<=n;i++) in[i]=INF; //建立最短边集
for (int i=0;i<m;i++) {
Edge e=edges[i];
if (e.to!=e.from && in[e.to]>e.cost) { //易错!! e.to!=e.from 自环
pre[e.to]=e.from;
in[e.to]=e.cost;
}
}
for (int i=1;i<=n;i++) {
if (i!=s&&in[i]==INF) return -1; //存在孤立点 不存在最小树形图
}
int np=in[s]=0;
memset(vis,-1,sizeof(vis));
memset(id,-1,sizeof(id));
for (int i=1;i<=n;i++) {
ret+=in[i];
int v=i;
while (vis[v]!=i&&id[v]==-1&&v!=s) { //不找到环 不存在其他环中 不是根
vis[v]=i;
v=pre[v];
}
if (id[v]==-1&&v!=s) { //找到有向环(因第一个条件而跳出while, 即满足后两个条件
) np++;
for (int u=pre[v];u!=v;u=pre[u]) {
id[u]=np;
}
id[v]=np;
}
}
if (np==0) break;
for (int i=1;i<=n;i++) //收集非环点
if (id[i]==-1) id[i]=++np;
for (int i=0;i<m;i++) { //重新处理边
int u=edges[i].from;
int v=edges[i].to;
edges[i].from=id[edges[i].from];
edges[i].to=id[edges[i].to];
if (edges[i].from!=edges[i].to) {
edges[i].cost-=in[v];
}
}
n=np; s=id[s]; //新点数,新起点
}
return ret;
}
int main()
{
init();
int ans=work(1);
if (~ans) printf("%d\n",ans);
else printf("impossible\n");
return 0;
}