Command Network POJ - 3164 有向图的最小生成树(最小树形图朱刘算法)

19 篇文章 0 订阅
19 篇文章 0 订阅

有向图的最小生成树(最小树形图朱刘算法)

题意
给你N个点的坐标和M条有向边,问你以点1为根的最小树形图的边权之和。

思路
套用朱-刘算法 求解最小树形图即可 复杂度O(EV)

设根结点为v0,

(1)求最短弧集合E0

  从所有以vi(i ≠ 0)为终点的弧中取一条最短的,若对于点i,没有入边,则不存在最小树形图,算法结束;如果能取,则得到由n个点和n-1条边组成的图G的一个子图G’,这个子图的权值一定是最小的,但是不一定是一棵树。

(2)检查E0

  若E0没有有向环且不包含收缩点,则计算结束,E0就是图G以v0为根的最小树形图;若E0含有有向环,则转入步骤(3);若E0没有有向环,但是存在收缩点,转到步骤(4)。

(3)收缩G中的有向环

  把G中的环C收缩成点u,对于图G中两端都属于C的边就会被收缩掉,其他弧仍然保留,得到一个新的图G1,G1中以收缩点为终点的弧的长度要变化。变化的规则是:设点v在环C中,且环中指向v的边的权值为w,点v’不在环C中,则对于G中的每一条边<v', v>,在G1中有边<v', u>和其对应,且权值WG1(<v', u>) = WG(<v', v>) - w;对于图G中以环C中的点为起点的边<v', v>,在图G1中有边<u, v'>,则WG1(<u, v'>) = WG(<v', v>)。有一点需要注意,在这里生成的图G1可能存在重边。

  对于图G和G1:

  ①如果图G1中没有以v0为根的最小树形图,则图G也没有;

  ②如果G1中有一v0为根的最小树形图,则可按照步骤(4)的展开方法得到图G的最小树形图。

所以,应该对于图G1代到(1)中反复求其最小树形图,直到G1的最小树形图u求出。

(4)展开收缩点

  假设图G1的最小树形图为T1,那么T1中所有的弧都属于图G的最小树形图T。将G1的一个收缩点u展开成环C,从C中去掉与T1具有相同终点的弧,其他弧都属于T。

代码
g++输出double要%f

/*
 * Author       :  Echo
 * Email        :  1666424499@qq.com  
 * Description  :   
 * Created Time :  2017/10/14 13:11:00
 * Last Modify  :  2017/10/14 13:57:34
 * File Name    :  liu_zhu.cpp
 */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
using namespace std;
const int maxn=110,maxm=10100;
const double INF=0x3f3f3f3f;
struct edge{
    int u,v;
    double w;
} e[maxm];//边
int n,m;//n个顶点,m条边
double ans;//ans存答案
int id[maxn];//标记环
int pre[maxn];//前驱
int vis[maxn];//判断是否有环
double inw[maxn];//边长

double x[maxn];
double y[maxn];

double dist(int a,int b){
    return sqrt( (x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]) );
}
void zhu_liu(int root){
    int s,t,idx=n;
    while(idx){
        for(int i=1;i<=n;++i) inw[i]=INF,id[i]=-1,vis[i]=-1;//初始化
        for(int i=1;i<=m;++i){//求最短弧集合
            s=e[i].u;t=e[i].v;
            if(e[i].w>inw[t] || s==t) continue;
            pre[t]=s;
            inw[t]=e[i].w;
        }
        inw[root]=0;pre[root]=root;
        for (int i=1;i<=n;++i){//判断是否有孤立点
            if(inw[i]==INF){
                printf("poor snoopy\n");
                return;
            }
            ans+=inw[i];
        }
        idx=0;
        for(int i=1;i<=n;++i)//查看是否有环
            if(vis[i]==-1){
                t=i;
                while(vis[t]==-1) vis[t]=i,t=pre[t];
                if(vis[t]!=i || t==root) continue;
                id[t]=++idx;
                for(s=pre[t];s!=t;s=pre[s]) id[s]=idx;//标记环
            }
        if(idx==0) continue;
        for(int i=1;i<=n;++i) //非环的即为单独成环
            if(id[i]==-1) id[i]=++idx;
        for(int i=1;i<=m;++i){//缩点 到达其的边,已加则少加。
            e[i].w-=inw[e[i].v];
            e[i].u=id[e[i].u];
            e[i].v=id[e[i].v];
        }
        n=idx;
        root=id[root];
    }
    printf("%.2f\n",ans);
}
int main(){
    while(~scanf("%d%d",&n,&m)){
        ans=0;
        for(int i=1;i<=n;i++)
            scanf("%lf%lf",&x[i],&y[i]);
        for (int i=1;i<=m;++i){
            scanf("%d%d",&e[i].u,&e[i].v);
            e[i].w=dist(e[i].u,e[i].v);
        }
        zhu_liu(1);
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值