Uva 1151 - Buy or Build (最小生成树+并查集)

题目链接 https://vjudge.net/problem/UVA-1151

【题意】
平面上有n个点(n <= 1000),你的任务是让这n个点联通,为此可以新建一些边,费用等于两个欧几里得距离的平方。另外还有q(0<=q<=8)个套餐可以购买,如果你购买了第i个套餐,该套餐中的所有结点变得相互联通。

【思路】
现求出原图的最小生成树,然后将最小生成树的边保存到另一个边集中,再依次枚举每种套餐的选择情况,如果选择了第i个套餐,那么就把第i个套餐中的点全部用并查集合并起来,最后再用kruskal算法求一次最小生成树,取总费用的最小值。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1050;
const int maxm = 500050;

int n, m, q;
int x[maxn], y[maxn], par[maxn];
int num[8], cost[8], node[8][maxn];

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

int dis(int x1, int x2, int y1, int y2) { return (x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2); }
bool cmp(Edge a, Edge b) { return a.dist < b.dist; }
int find(int a) { return par[a] == a ? a : par[a] = find(par[a]); }

int kruskal() {
    int ans = 0;
    for (int i = 0; i < n - 1; ++i) {
        int a = find(mst[i].from);
        int b = find(mst[i].to);
        if (a != b) {
            par[a] = b;
            ans += mst[i].dist;
        }
    }
    return ans;
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        scanf("%d%d", &n, &q);
        for (int i = 0; i < q; ++i) {
            scanf("%d%d", &num[i], &cost[i]);
            for (int j = 0; j < num[i]; ++j) {
                scanf("%d", &node[i][j]);
                --node[i][j];
            }
        }
        for (int i = 0; i < n; ++i) { scanf("%d%d", &x[i], &y[i]); }
        m = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                int dist = dis(x[i], x[j], y[i], y[j]);
                edges[m++] = Edge(i, j, dist);
            }
        }
        sort(edges, edges + m, cmp);
        int cnt = 0, ans = 0;
        for (int i = 0; i < n; ++i) par[i] = i;
        for (int i = 0; cnt < n - 1; ++i) {
            Edge e = edges[i];
            int a = find(e.from);
            int b = find(e.to);
            if (a != b) {
                par[a] = b;
                mst[cnt++] = e;
                ans += e.dist;
            }
        }

        for (int s = 0; s < (1 << q); ++s) {
            int tmp = 0;
            for (int i = 0; i < n; ++i) par[i] = i;
            for (int i = 0; i < q; ++i) {
                if ((s >> i) & 1) {
                    tmp += cost[q - 1 - i];
                    for (int j = 0; j < num[q - 1 - i] - 1; ++j) {//选中套餐后在并查集中把套餐中的点合并
                        par[find(node[q - 1 - i][j])] = find(node[q - 1 - i][j + 1]);
                    }
                }
            }
            ans = min(ans, tmp + kruskal());
        }
        printf("%d\n", ans);
        if (t) putchar('\n');
    }
    return 0;
}
posted @ 2018-02-05 13:26  不想吃WA的咸鱼  阅读(118)  评论(0编辑  收藏  举报