[POJ 3164]Command Network[最小树形图]

题目链接:[POJ 3164]Command Network[最小树形图]
题意分析:
求从根节点出发,到达所有其他节点的边所构成权值最小的生成子图权值是多少? (单向边)
解题思路:
无向图的最小生成树再熟悉不过了,这次变成了有向图上固定结点的最小生成树,引入一个算法:朱-刘算法,没错,就是这个名字,Made In China~。
首先看看《挑战程序设计》对最小树形图的定义:
[POJ 3164]Command Network[最小树形图]_第1张图片
具体算法流程就不描述了,写在代码里了。
这着重说说两点:

  • 为什么可以依靠结点是否没有入边来判断是否存在最小树形图?(可能有人会认为,存在一种样例,根节点没有指向其他结点的出边而只有入边,其他点均有入边)——回答: 对哒,确实存在。此时不会判定不存在,但是其他点一定构成了环(画图看看)。而后期的缩点,不断重复过程,最终这组样例会出现没有环的情况,而此时,就会存在点没有入边,返回-1。
  • 为什么新图中需要对edge[i].w进行减法?——-回答:这样在选择了这条边后,减去了这个节点在原来图中的入边权值,间接等价于选择了这条边放弃了那条原来的边。

个人感受:
算了入了点门了2333
具体代码如下:

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

const int INF = 0x7f7f7f7f;
const int MAXN = 111;

struct Edge{
    int u, v;
    double w;
}edge[MAXN * MAXN];

int n, m, pre[MAXN], newid[MAXN], vis[MAXN];
double x[MAXN], y[MAXN], in[MAXN];

double getdis(int a, int b)
{
    double delx = x[a] - x[b], dely = y[a] - y[b];
    return sqrt(delx * delx + dely * dely);
}

double zhuLiu(int rt)
{
    double ret = 0;
    while (1)
    {
        for (int i = 0; i < n; ++i) in[i] = INF;
        // 第一步:找出每个点的最小权入边
        for (int i = 0; i < m; ++i)
        {
            int u = edge[i].u, v = edge[i].v;
            if (in[v] > edge[i].w && u != v) in[v] = edge[i].w, pre[v] = u;
        }
        for (int i = 0; i < n; ++i)
        {
            if (i == rt) continue;
            if (in[i] == INF) return -1; // 判断是否无法构成最小树形图
        }
        // 第二步:判环
        int cnt = 0;
        memset(newid, -1, sizeof newid);
        memset(vis, -1, sizeof vis);
        in[rt] = 0;
        for (int i = 0; i < n; ++i)
        {
            ret += in[i];
            int v = i;
            while (vis[v] != i && newid[v] == -1 && v != rt) // 找环。vis[]用来标记点在哪个点为首的环中
            {
                vis[v] = i;
                v = pre[v];
            }
            if (v != rt && newid[v] == -1) // 找到环了,缩点
            {
                for (int u = pre[v]; u != v; u = pre[u]) newid[u] = cnt;
                newid[v] = cnt++;
            }
        }
        if (cnt == 0) break; // 没有环

        for (int i = 0; i < n; ++i) // 重新赋予其他点标号
            if (newid[i] == -1) newid[i] = cnt++;

        for (int i = 0; i < m; ++i) // 建立新图
        {
            int u = edge[i].u, v = edge[i].v;
            edge[i].u = newid[u];
            edge[i].v = newid[v];
            if (newid[u] != newid[v]) edge[i].w -= in[v]; // 选择当前边的同时便放弃了原来的最小入边.原来的已经加到ret中了
        }
        n = cnt;
        rt = newid[rt];
    }
    return ret;
}

int main()
{
    while (~scanf("%d%d", &n, &m))
    {
        for (int i = 0; i < n; ++i) scanf("%lf%lf", &x[i], &y[i]);
        for (int i = 0; i < m; ++i)
        {
            scanf("%d%d", &edge[i].u, &edge[i].v);
            --edge[i].u;
            --edge[i].v;
            edge[i].w = getdis(edge[i].u, edge[i].v);
        }

        double ans = zhuLiu(0);
        if (ans != -1) printf("%.2f\n", ans);
        else printf("poor snoopy\n");
    }
    return 0;
}

你可能感兴趣的:(最小树形图)