最小树形图(模板)

       最小树形图又称最小有向生成树,简单来说就是有向图的最小生成树,需要给定根结点,除了根结点的入度为0以外,其它结点的入度都为1,并且可以从根结点出发访问到所有结点

        最小树形图的求解算法叫做朱刘算法,下面两篇博客的讲解非常详细,这里我就只把模板代码贴过来了

        http://blog.csdn.net/wsniyufang/article/details/6747392
        http://blog.csdn.net/sdj222555/article/details/7459738

const int inf = 2e9;
const int maxn = 105;
const int maxm = 100050;

int n, m;//顶点数,边数  
int in[maxn];//in[u]记录当前图中指向u结点的所有边权中最小的那条边权 
int pre[maxn];//pre[u]记录最小边权对应的父亲结点 
int used[maxn], id[maxn];//used是访问标记数组,id[u]是计算出u在下一次的新图中的编号 

struct Edge {
    int from, to;
    int dist;
    Edge(int f = 0, int t = 0, int d = 0) :from(f), to(t), dist(d) {}
}edges[maxm];//边集 

int direct_mst(int root, int V, int E) {//三个参数分别是根结点,顶点数量,边数量 
    int ans = 0;
    while (1) {
        //为每个非根结点选出最小入边 
        for (int i = 0; i < V; ++i) in[i] = inf;
        for (int i = 0; i < E; ++i) {
            int u = edges[i].from;
            int v = edges[i].to;
            if (in[v] > edges[i].dist && u != v) {
                in[v] = edges[i].dist;
                pre[v] = u;
            }
        }
        //判断连通性,如有不可达结点说明无解
        for (int i = 0; i < V; ++i) {
            if (i == root) continue;
            if (inf == in[i]) return -1;
        }

        //判断有向环是否存在,存在有向环就缩圈
        int cnt = 0;//生成新图的结点编号
        memset(id, -1, sizeof(id));//id[u]==-1表示结点u还不属于任何一个自环 
        memset(used, -1, sizeof(used));
        in[root] = 0;
        for (int i = 0; i < V; ++i) {
            ans += in[i];
            int v = i;
            while (used[v] != i && id[v] == -1 && v != root) {//每个结点不断向上寻找父亲结点,要么找到根结点,要么形成一个自环 
                used[v] = i;
                v = pre[v];
            }
            if (v != root && id[v] == -1) {//找到了自环,进行缩点,更新id数组 
                for (int u = pre[v]; u != v; u = pre[u]) id[u] = cnt;
                id[v] = cnt++;
            }
        }

        if (0 == cnt) break;//没有自环说明已经求出最终结果

        //建立新图
        for (int i = 0; i < V; i++)
            if (id[i] == -1) id[i] = cnt++;//先把不在自环中的点的编号更新  

        for (int i = 0; i < E; i++) {
            int u = edges[i].from;
            int v = edges[i].to;
            edges[i].from = id[u];
            edges[i].to = id[v];
            if (id[u] != id[v]) edges[i].dist -= in[v];
            //这里id[u] != id[v]说明edges[i]这条边原来不在有向环中,
            //如果这条边指向了有向环,那么它的边权就要减少in[v]等价于整个环的边权减去in[v]
            //而如果没有指向有向环,说明它与这个有向环毫无关系,那么在之前的寻找自环缩点过
            //程中已经把这条边的权值加上了,所以这里避免重复计算让这条边的权值减小in[v]变为0 
        }
        V = cnt;
        root = id[root];
    }
    return ans;
}

模板题 poj3164
这题就是裸的最小树形图,但是输出好坑,用double型变量记录结果的时候,最终输出要用
printf(“%.2f\n”,ans) 能AC; 要是写成printf(“%.2lf\n”,ans) 就是WA的,一脸懵逼……

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const double inf = 2e9;
const int maxn = 105;
const int maxm = 10050;

int n, m;//顶点数,边数 
double x[maxn], y[maxn];//坐标 
double in[maxn];//in[u]记录当前图中指向u结点的所有边权中最小的那条边权 
int pre[maxn];//pre[u]记录最小边权对应的父亲结点 
int used[maxn], id[maxn];//used是访问标记数组,id[u]是计算出u在下一次的新图中的编号 

struct Edge {
    int from, to;
    double dist;
    Edge(int f = 0, int t = 0, double d = 0) :from(f), to(t), dist(d) {}
}edges[maxm];//边集 

double dis(double x1, double y1, double x2, double y2) {
    return sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2));
}

double direct_mst(int root, int V, int E) {//三个参数分别是根结点,顶点数量,边数量 
    double ans = 0;
    while (1) {
        //为每个非根结点选出最小入边 
        for (int i = 0; i < V; ++i) in[i] = inf;
        for (int i = 0; i < E; ++i) {
            int u = edges[i].from;
            int v = edges[i].to;
            if (in[v] > edges[i].dist && u != v) {
                in[v] = edges[i].dist;
                pre[v] = u;
            }
        }
        //判断连通性,如有不可达结点说明无解
        for (int i = 0; i < V; ++i) {
            if (i == root) continue;
            if (inf == in[i]) return -1;
        }

        //判断有向环是否存在,存在有向环就缩圈
        int cnt = 0;//生成新图的结点编号
        memset(id, -1, sizeof(id));//id[u]==-1表示结点u还不属于任何一个自环 
        memset(used, -1, sizeof(used));
        in[root] = 0;
        for (int i = 0; i < V; ++i) {
            ans += in[i];
            int v = i;
            while (used[v] != i && id[v] == -1 && v != root) {//每个结点不断向上寻找父亲结点,要么找到根结点,要么形成一个自环 
                used[v] = i;
                v = pre[v];
            }
            if (v != root && id[v] == -1) {//找到了自环,进行缩点,更新id数组 
                for (int u = pre[v]; u != v; u = pre[u]) id[u] = cnt;
                id[v] = cnt++;
            }
        }

        if (0 == cnt) break;//没有自环说明已经求出最终结果

        //建立新图
        for (int i = 0; i < V; i++)
            if (id[i] == -1) id[i] = cnt++;//先把不在自环中的点的编号更新  

        for (int i = 0; i < E; i++) {
            int u = edges[i].from;
            int v = edges[i].to;
            edges[i].from = id[u];
            edges[i].to = id[v];
            if (id[u] != id[v]) edges[i].dist -= in[v];
            //这里id[u] != id[v]说明edges[i]这条边原来不在有向环中,
            //如果这条边指向了有向环,那么它的边权就要减少in[v]等价于整个环的边权减去in[v]
            //而如果没有指向有向环,说明它与这个有向环毫无关系,那么在之前的寻找自环缩点过
            //程中已经把这条边的权值加上了,所以这里避免重复计算让这条边的权值减小in[v]变为0 
        }
        V = cnt;
        root = id[root];
    }
    return ans;
}

int main() {
    while (scanf("%d%d", &n, &m) == 2) {
        for (int i = 0; i < n; ++i) scanf("%lf%lf", &x[i], &y[i]);
        for (int i = 0; i < m; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            --u, --v;
            if (u == v) {//去除自环,权值设为无穷大 
                edges[i] = Edge(u, v, inf);
                continue;
            }
            double d = dis(x[u], y[u], x[v], y[v]);
            edges[i] = Edge(u, v, d);
        }
        double ans = direct_mst(0, n, m);
        if (ans == -1) printf("poor snoopy\n");
        else printf("%.2f\n", ans);
        //else printf("%.2lf\n", ans);这么写会WA
    }
    return 0;
}
posted @ 2018-03-13 17:42  不想吃WA的咸鱼  阅读(186)  评论(0编辑  收藏  举报