poj 3164 朱刘算法(最小树形图)

题意:裸题。所谓最小树形图:给定一个带权有向图和一个固定结点n,求从n出发能到达所有点的最小权生成树。

思路:朱刘算法,最坏情况复杂度位O(VE)。后有优化算法降低了复杂度。

算法步骤如下:(本文不再证明,参考下面给出的我自己画的一个图即可理解)
1.判断图的连通性,若不连通直接无解,否则一定有解。
2.为除了根节点以外的所有点选择一个权值最小的入边,假设用pre数组记录前驱,f数组记录选择的边长,记所选边权和为temp。
3.(可利用并查集)判断选择的的边是否构成环,若没有则直接ans+=temp并输出ans,若有,则进行下一步操作。
4.对该环实施缩点操作,设该环上有点V1,V2……Vi……Vn,缩成的点为node ,对于所有不在环中的点P进行如下更改:
(1) 点P到node的距离为min{a[p,Vi]-f[Vi]} (a为边集数组)
(2)点node到p的距离为min{a[Vi,p]}
操作(1)的理解:先假设环上所有边均选上,若下次选择某一条边进入该环,则可以断开进入点与进入点的前驱之间的边,即断开F[进入点],所以等效为直接把a[p,node]赋值为min{a[p,Vi]-f[Vi]}。
特别提醒:本题有自环,可以提前删掉,因为它没有用。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
#define clr(s,t) memset(s,t,sizeof(s));
#define INF 0x3fffffff
#define N 105
#define M 10005
struct point{
    double x,y;
}p[N];
struct edge{
    int x,y,next;
    double w;
}e[M];
int first[N],top,n,m,used[N],f[N],pre[N],root;
double in[N];
double dist(point a,point b){
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
void add(int x,int y,double w){
    e[top].x = x;
    e[top].y = y;
    e[top].w = w;
    e[top].next = first[x];
    first[x] = top++;
}
double zhu_liu(){
    int i,num,a,b;
    double res = 0;
    root = 1;
    while(1){
        for(i = 1;i<=n;i++)
            in[i] = INF;
        for(i = 0;i<top;i++)        //遍历每个点,寻找权值最小的入边
            if(e[i].w < in[e[i].y] && e[i].x!=e[i].y){
                in[e[i].y] = e[i].w;
                pre[e[i].y] = e[i].x;
            }
        for(i = 1;i<=n;i++)//如果有除root的点没有入边,则必无最小树形图
            if(i != root && in[i] == INF)
                return 0;
        clr(f, -1);
        clr(used, -1);
        num = in[root] = 0;
        for(i = 1;i<=n;i++){
            res += in[i];           //结果累加
            b = i;
            while(used[b]!=i && b!=root && f[b]==-1){
                                    //找圈,从当前点往回找只有三种可能,找到根、找到另外一个已经找到的圈,或者形成一个新圈
                used[b] = i;
                b = pre[b];
            }
            if(b!=root && f[b]==-1){//标记新找到的圈
                f[b] = ++num;
                for(a = pre[b];a!=b;a=pre[a])
                    f[a] = num;
            }
        }
        if(!num)                    //若无圈,则直接返回
            break;
        for(i = 1;i<=n;i++)
            if(f[i] == -1)
                f[i] = ++num;
        for(i = 0;i<top;i++){       //缩圈为点
            if(f[e[i].x] != f[e[i].y])
                e[i].w -= in[e[i].y];
            e[i].x = f[e[i].x];
            e[i].y = f[e[i].y];
        }
        n = num;
        root = f[root];
    }
    return res;
}
int main(){
    while(scanf("%d %d",&n,&m)!=EOF){
        int i,a,b;
        double tmp;
        top = 0;
        clr(first, -1);
        for(i = 1;i<=n;i++)
            scanf("%lf %lf",&p[i].x,&p[i].y);
        for(i = 1;i<=m;i++){
            scanf("%d %d",&a,&b);
            if(a!=b)
                add(a,b,dist(p[a],p[b]));
        }
        if(n==1){
            printf("0.00\n");
            continue;
        }
        tmp = zhu_liu();
        if(tmp)
            printf("%.2lf\n",tmp);
        else
            printf("poor snoopy\n");
    }
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值