最小树形图(刘朱算法)记录

算法步骤:

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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值