题解 UVA1151Buy or Build (最小生成树)

题意

平面上有n(\(n\leq1000\))个点,你的任务是让所有n个点联通。为此,你可以新建一些边,费用等于两个端点的欧几里得距离平方。另外还有q(\(q\leq8\))个套餐可以购买,如果你购买了第i个套餐,该套餐中的所有结点将变得相互连接。第i个套餐的花费为\(C_i\)

算法

最小生成树

分析

朴素算法:枚举要选择哪些套餐,跑最小生成树;

复杂度:枚举:O(\(2^q\)) 排序O(\(n^2\)\(logn\)) 总O(\(2^q\)\(n^2\) \(+\) \(n^2\)\(logn\)); 显然是接受不了的

优化:先求一遍Kruskal,得到了\(n-1\)条边,然后枚举套餐,跑最小生成树时只考虑这\(n-1\)条边

简单证明:买了套餐后,相当于一些边的边权变成了0,而对于不在套餐中的每条边e,排序在e之前的边一个都没少,反而还多了一些权值为0的边,所以在原图Kruskal时被“扔掉”的边,在后面的枚举套餐中也一样会被扔掉。

代码

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1010, maxm = 1e6 + 10;
int n,fa[maxn],ans,sum,q,x[maxn],y[maxn],w[maxn][maxn],num,c[maxn],_cnt;
struct Edge{
    int from,to,val;
}e[maxm];
Edge r[maxn]; //要用的边

bool cmp(Edge x, Edge y){return x.val < y.val;}

int get(int x){return fa[x] == x ? x : fa[x] = get(fa[x]);}

void Kruskal0(){
    int cnt = 0;
    sort(e + 1, e + 1 + num, cmp);
    for(int i = 1; i <= num; ++ i){
        int u = e[i].from, v = e[i].to;
        int q1 = get(u), q2 = get(v);
        if(q1 != q2){
            fa[q1] = q2; cnt ++;
            r[cnt] = e[i]; ans += e[i].val;  //保留要用的边
        }
        if(cnt >= n - 1) break;
    }
    num = cnt;
}

void Kruskal(){
    for(int i = 1; i <= num; ++ i){
        int q1 = get(r[i].from), q2 = get(r[i].to);
        if(q1 != q2){
            fa[q1] = q2; _cnt ++;
            sum += r[i].val;
        }
        if(_cnt >= n - 1) break;
    }
}

void get_ans(){
    for(int ss = 0; ss < (1 << q); ++ ss){  //状压枚举
        sum = 0; _cnt = 0;
        for(int i = 1; i <= n; ++ i) fa[i] = i;
        for(int k = 1; k <= q; ++ k)
            if(ss & (1 << k - 1)){
                sum += c[k];
                for(int i = 1; i < w[k][0]; ++ i)
                    for(int j = i + 1; j <= w[k][0]; ++ j){
                        int q1 = get(w[k][i]), q2 = get(w[k][j]);
                        if(q1 != q2) fa[q1] = q2, _cnt ++;
                    }
            }
        Kruskal();
        ans = min(ans, sum);
    }
}

int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        sum = ans = num = 0;
        scanf("%d%d", &n, &q);
        for(int i = 1; i <= n; ++ i) fa[i] = i;
        for(int i = 1; i <= q; ++ i){
            scanf("%d%d", &w[i][0], &c[i]);
            for(int j = 1; j <= w[i][0]; ++ j) scanf("%d", &w[i][j]);
        }
        for(int i = 1; i <= n; ++ i) scanf("%d%d", &x[i], &y[i]);
        for(int i = 1; i < n; ++ i)
            for(int j = i + 1; j <= n; ++ j){
                int dis = (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]);
                e[++num].from = i, e[num].to = j, e[num].val = dis;
            }
        Kruskal0();
        get_ans();
        printf("%d\n", ans);
        if(T) cout << endl;   //UVA的神仙格式
    }
    return 0;
}
posted @ 2020-10-09 11:56  When_C  阅读(81)  评论(0编辑  收藏  举报