《挑战程序设计竞赛》2.5.2 最小生成树 POJ3723 3169 1258 2377 2395 AOJ2224(1)

POJ3723

http://poj.org/problem?id=3723

题意

windy要组建一支军队,召集了N个女孩和M个男孩,每个人要付10000RMB,但是如果一个女孩和一个男孩有关系d的,且已经付给了其中一个人的钱,那么就可以付给另一个人10000-d元,求windy最少要付多少钱。

思路

题目所给的数据是两两之间的连通关系,比较适合用kruskal+并查集求解。
但这个题要求的是最大生成树,不是最小生成树哦,需要修改比较条件。当然将d取反再求最小生成树也是一样的。

代码

Source Code

Problem: 3723       User: liangrx06
Memory: 996K        Time: 360MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 20000;
const int R = 50000;

struct Node {
    int x, y, d;
};

int n, r;
Node node[R+1];
int pre[N+1];
int rank[N+1];

bool cmp(const Node& a, const Node& b)
{
    return a.d < b.d;
}

void init()
{
    for (int i = 1; i <= n; i ++) {
        pre[i] = i;
        rank[i] = 0;
    }
}

int find(int a)
{
    while (a != pre[a])
        a = pre[a];
    return a;
}

void unite(int a, int b)
{
    a = find(a);
    b = find(b);
    if (a == b) return;
    if (rank[a] < rank[b]) {
        pre[a] = b;
    } else {
        pre[b] = a;
        if (rank[a] == rank[b])
            rank[a] ++;
    }
}

bool same(int a, int b)
{
    return find(a) == find(b);
}

int kruskal()
{
    sort(node+1, node+r+1, cmp);
    int res = 0;
    for (int i = 1; i <= r; i ++) {
        int x = node[i].x, y = node[i].y;
        if (!same(x, y)) {
            unite(x, y);
            res += node[i].d;
        }
    }
    return res;
}

int main(void)
{
    int t, n1, n2;
    cin >> t;
    while (t--) {
        cin >> n1 >> n2 >> r;
        n = n1 + n2;
        int x, y, d;
        for (int i = 1; i <= r; i ++) {
            scanf("%d%d%d", &x, &y, &d);
            node[i].x = x + 1;
            node[i].y = n1 + y + 1;
            node[i].d = -d;
        }
        init();
        printf("%d\n", 10000 * n + kruskal());
    } 

    return 0;
}   

POJ3169

POJ1258

http://poj.org/problem?id=1258

题意

有n个农场,已知这n个农场都互相相通,有一定的距离,现在每个农场需要装光纤,问怎么安装光纤能将所有农场都连通起来,并且要使光纤距离最小,输出安装光纤的总距离。

思路

求最小生成树,由于这个题直接给出了邻接矩阵,所以用prim算法比较合适。

代码

Source Code

Problem: 1258       User: liangrx06
Memory: 212K        Time: 16MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
using namespace std;

const int N = 100;
const int INF = 0x3f3f3f3f;

int n;
int d[N][N];
int md[N];
bool used[N];

int prim()
{
    fill(md, md+n, INF);
    fill(used, used+n, false);
    md[0] = 0;
    int res = 0;
    while (true) {
        int v = -1;
        for (int i = 0; i < n; i ++) {
            if (!used[i] && (v == -1 || md[i] < md[v]))
                v = i;
        }
        if (v == -1) break;
        used[v] = true;
        res += md[v];
        for (int i = 0; i < n; i ++) {
            md[i] = min(md[i], d[v][i]);
        }
    }
    return res;
}

int main(void)
{
    while (scanf("%d", &n) != EOF) {
        for (int i = 0; i < n; i ++) {
            for (int j = 0; j < n; j ++) {
                scanf("%d", &d[i][j]);
            }
        }
        printf("%d\n", prim());
    } 

    return 0; 
} 

POJ2377

http://poj.org/problem?id=2377

题意

给定一些两点之间的连通距离,求最大生成树。

思路

题目所给的数据是两两之间的连通关系,比较适合用kruskal+并查集求解。
但这个题要求的是最大生成树,排序的时候按降序排列或者将距离取反最后结果再取反就可以了。

代码

Source Code

Problem: 2377       User: liangrx06
Memory: 496K        Time: 32MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 1000;
const int M = 20000;

struct Node {
    int x, y, d;
};

int n, m;
Node node[M+1];
int pre[N+1];
int rank[N+1];

bool cmp(const Node& a, const Node& b)
{
    return a.d < b.d;
}

void init()
{
    for (int i = 1; i <= n; i ++) {
        pre[i] = i;
        rank[i] = 0;
    }
}

int find(int a)
{
    while (a != pre[a])
        a = pre[a];
    return a;
}

void unite(int a, int b)
{
    a = find(a);
    b = find(b);
    if (a == b) return;
    if (rank[a] < rank[b]) {
        pre[a] = b;
    } else {
        pre[b] = a;
        if (rank[a] == rank[b])
            rank[a] ++;
    }
}

bool same(int a, int b)
{
    return find(a) == find(b);
}

int kruskal()
{
    sort(node+1, node+m+1, cmp);
    int res = 0;
    int uniteTime = 0;
    for (int i = 1; i <= m; i ++) {
        int x = node[i].x, y = node[i].y;
        if (!same(x, y)) {
            unite(x, y);
            uniteTime ++;
            res += node[i].d;
        }
    }
    if (uniteTime == n-1)
        return -res;
    else
        return -1;
}

int main(void)
{
    cin >> n >> m;
    for (int i = 1; i <= m; i ++) {
        scanf("%d%d%d", &node[i].x, &node[i].y, &node[i].d);
        node[i].d *= -1;
    }
    init();
    printf("%d\n", kruskal());

    return 0;
}

POJ2395

http://poj.org/problem?id=2395

题意

有N个农场,它们是连通的,现在你要从1号农场找到路走到其他所有农场去。但是有个要求就是你必须使得你将要走的单段路的最大长度最小。也就是说,任意两个农场之间的路如果被你选中要走的话,那么这种单段路的最大值必须尽量小。

思路

其实就是要你选一些路使得所有农场属于同一个连通分量,且要求你输出最大边的值. 仔细想想kruskal算法,它从所有边长从小到大的顺序开始选边,每次选边都使得两个点连通. 可以证明kruskal算法所选择的最后一条边即是我们本题的答案.

最小瓶颈生成树在刘汝佳<<训练指南>>P343页有详细的介绍.

无向图G的一颗瓶颈生成树(bottleneck spanning tree)T是这样的一颗生成树,它最大的边权值在G的所有生成树中是最小的。

无向图的最小生成树一定是瓶颈生成树,但瓶颈生成树不一定是最小生成树。(来自百度百科:瓶颈生成树)

代码

Source Code

Problem: 2395       User: liangrx06
Memory: 388K        Time: 79MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 2000;
const int M = 10000;

struct Node {
    int x, y, d;
};

int n, m;
Node node[M+1];
int pre[N+1];
int rank[N+1];

bool cmp(const Node& a, const Node& b)
{
    return a.d < b.d;
}

void init()
{
    for (int i = 1; i <= n; i ++) {
        pre[i] = i;
        rank[i] = 0;
    }
}

int find(int a)
{
    while (a != pre[a])
        a = pre[a];
    return a;
}

void unite(int a, int b)
{
    a = find(a);
    b = find(b);
    if (a == b) return;
    if (rank[a] < rank[b]) {
        pre[a] = b;
    } else {
        pre[b] = a;
        if (rank[a] == rank[b])
            rank[a] ++;
    }
}

bool same(int a, int b)
{
    return find(a) == find(b);
}

int kruskal()
{
    sort(node+1, node+m+1, cmp);
    int res = 0;
    int uniteTime = 0;
    for (int i = 1; i <= m; i ++) {
        int x = node[i].x, y = node[i].y;
        if (!same(x, y)) {
            unite(x, y);
            uniteTime ++;
            res = node[i].d;
        }
    }
    if (uniteTime == n-1)
        return res;
    else
        return -1;
}

int main(void)
{
    cin >> n >> m;
    for (int i = 1; i <= m; i ++) {
        scanf("%d%d%d", &node[i].x, &node[i].y, &node[i].d);
    }
    init();
    printf("%d\n", kruskal());

    return 0;
}

AOJ2224

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2224

题意

有N个木桩M个栅栏,栅栏连接木桩,现在这些栅栏围成的封闭空间里有至少一只猫,要求破环若干个栅栏救出猫,问破环栅栏的最小长度。

思路

题目要求栅栏不组成圈,又要求破坏栅栏的最小长度,那么剩下的栅栏自然是最大生成树了(当然前提是图是连通的)。kruskal+并查集求最大生成树,在从大到小添加边的过程中,如果发现新添加的边的两个节点已经连通,说明这个边需要去掉,累加这个边的长度到结果变量res。
另外,即使这个图非连通,这样的破圈求解过程仍然能够得到正确答案。

代码

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

const int N = 10000;
const int M = 10000*10000/2;

struct Node {
    int x, y;
    double d;
};

int n, m;
Node node[M+1];
int pre[N+1];
int rank[N+1];

bool cmp(const Node& a, const Node& b)
{
    return a.d > b.d;
}

void init()
{
    for (int i = 1; i <= n; i ++) {
        pre[i] = i;
        rank[i] = 0;
    }
}

int find(int a)
{
    while (a != pre[a])
        a = pre[a];
    return a;
}
void unite(int a, int b)
{
    a = find(a);
    b = find(b);
    if (a == b) return;
    if (rank[a] < rank[b]) {
        pre[a] = b;
    } else {
        pre[b] = a;
        if (rank[a] == rank[b])
            rank[a] ++;
    }
}

bool same(int a, int b)
{
    return find(a) == find(b);
}

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

double kruskal()
{
    sort(node+1, node+m+1, cmp);
    double res = 0;
    for (int i = 1; i <= m; i ++) {
        int x = node[i].x, y = node[i].y;
        if (!same(x, y))
            unite(x, y);
        else
            res += node[i].d;
    }
    return res;
}

int main(void)
{
    int a[N+1], b[N+1];
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) {
        scanf("%d%d", &a[i], &b[i]);
    }
    for (int i = 1; i <= m; i ++) {
        scanf("%d%d", &node[i].x, &node[i].y);
        int x = node[i].x, y = node[i].y;
        node[i].d = distance(a[x], b[x], a[y], b[y]);
    }
    init();
    printf("%.3lf\n", kruskal());

    return 0;
}

你可能感兴趣的:(最小生成树,poj,Prim,kruskal,挑战程序设计竞赛)