POJ 3164 Command Network(最小树形图)

题目链接:
POJ 3164 Command Network
题意:
给出n个点[下标从1–n]的坐标和m条单向边[i,j]表示可以从i点建一条边到j,权值是两点距离,
求将这n个点连通的最小边权和。如果无法连通输出”poor snoopy”,否则输出最小边权和。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 110;
const int MAX_M = 10010;
const double INF = 1e20;
const double eps = 1e-8;

int n, m;
int NV, NE; //NV和NE分别是结点和边个数(结点下标从0--NV - 1,边下标从0-- NE - 1)
int ID[MAX_N], vis[MAX_N], pre[MAX_N]; //结点重新编号后的编号、寻找环时是否访问、结点的前驱结点
double  In[MAX_N]; //结点入度最小值

struct Point{
    double x, y;

    Point() {}
    Point(double _x, double _y) : x(_x),y(_y) {}

    double dis(const Point& rhs) const {
        return hypot(x - rhs.x, y - rhs.y);
    }
}point[MAX_N];

struct Edge{
    int u, v;
    double w;

    Edge() {}
    Edge(int _u, int _v, double _w) :u(_u), v(_v), w(_w) {} 
}edge[MAX_M];

double Directed_MST(int root)
{
    double res = 0, w;
    int u, v;
    while(1){
        //1.找最小入边
        for(int i = 0; i < NV; i++) In[i] = INF; //将所有结点的入度设为-1
        for(int i = 0; i < NE; i++){
            u = edge[i].u;
            v = edge[i].v;
            w = edge[i].w;
            if(u != v && w < In[v]){ //消除自环影响
                In[v] = w;
                pre[v] = u;
            }
        }
        for(int i = 0; i < NV; i++){ //检查是否存在除root孤立点,若存在则不存在最小树形图
            if(i == root) continue;
            if(In[i] == INF) return -1; //i是孤立点
        }
        //2.找环
        int cnt = 0; //记录所有结点重新标号后新结点数量
        memset(vis, -1, sizeof(vis));
        memset(ID, -1, sizeof(ID));
        In[root] = 0; //将根节点入度重设为0
        for(int i = 0; i < NV; i++){ //遍历所有结点找环
            res += In[i]; //结果加上当前结点的最少入度
            v = i;
            while(vis[v] != i && ID[v] == -1 && v != root){ //将v所在有向环(或有向单链)上的所有结点收缩为i结点
                vis[v] = i;
                v = pre[v];
            }
            if(v != root && ID[v] == -1){ //只有有向环时重新标号
                for(int u = pre[v]; u != v; u = pre[u]){ //将i所在有向环上所有结点收缩为结点cnt
                    ID[u] = cnt;
                }
                ID[v] = cnt++;
            }
        }
        if(cnt == 0) break; //只剩下了root所在的有向环
        for(int i = 0; i < NV; ++i){
            if(ID[i] == -1) ID[i] = cnt++; //root所在有向环上结点或者其他有向环外接单链上点重新标号
        }
        //3.建新图
        for(int i = 0; i < NE; i++){ //更新其他点到环上的距离
            u = edge[i].u;
            v = edge[i].v;
            edge[i].u = ID[u];
            edge[i].v = ID[v];
            if(edge[i].u != edge[i].v){ //收缩点后不是同一结点
                edge[i].w -= In[v];
            }
        }
        NV = cnt; //更新新图的结点数量和根节点编号
        root = ID[root];
    }
    return res;
}

int main()
{
    while(~scanf("%d%d", &n ,&m)){
        NV = n, NE = m;
        for(int i = 1; i <= n; ++i){
            scanf("%lf%lf", &point[i].x, &point[i].y);
        }
        for(int i = 0; i < m; ++i){
            int u, v;
            scanf("%d%d", &u, &v);
            if( u != v) edge[i].w = point[u].dis(point[v]);
            else edge[i].w = INF;
            edge[i].u = u - 1, edge[i].v = v - 1; //保证结点下标从0--NV-1
        }
        double ans = Directed_MST(0);
        if(fabs(ans + 1) <= eps) printf("poor snoopy\n");
        else printf("%.2f\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值