UVA 1151 Buy or Build (最小生成树)

本文介绍了一种在给定点坐标的情况下使用最小生成树算法来连接所有点并优化边的选取策略,通过预先利用某些已知的连接信息进一步减少总成本的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

告诉你n 个点的坐标,你要在两个点之间连线,使得点全部相同,连边的费用为这两个点的欧几里得距离,你的目的是使这个费用最低,并且你有q(q <= 8) 个套餐可以用,一个套餐中会告诉你一些点已经连通, 问最少花费?

思路:

全部的点相通,很明显是最小生成树。

最容易想到的是,暴力枚举哪一个套餐用,哪一个套餐不用,在求最小生成树,这样会超时,因为原图是一个完全图,有100W个边。

有个小优化:

我们可以先求一边最小生成树,n-1个边,在n-1个边中在暴力枚举套餐。 这样边少了很多很多。

暴力枚举套餐,可以用二进制枚举子集的方法。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdlib>
#define Siz(x) (int)x.size()
#define Max(a,b)((a)>(b)?(a):(b))
#define Min(a,b)((a)>(b)?(b):(a))
#define ps push_back
#define sqr(x)((x)*(x))
using namespace std;

const int maxn = 1000 + 7;
const int inf = 0x3f3f3f3f;

typedef long long LL;
int T, n, q, num_edge;
vector<int>c[10];
int w[10],fa[maxn];
//double dist[maxn][maxn];


struct Node{
    int x, y;
    void read(){
        scanf("%d %d",&x, &y);
    }
}nod[maxn];



struct edge{
    int f,t;
    int d;
    int flag;
    bool operator < (const edge& rhs) const {
        return d < rhs.d;
    }
}p[maxn*maxn];

void addedge(int x,int y,int d){
    p[num_edge].f = x;
    p[num_edge].t = y;
    p[num_edge].flag = 0;
    p[num_edge++].d = d;
}
void DIST(){
    num_edge = 0;
    for (int i = 1; i <= n; ++i){
        for (int j = i+1; j <= n; ++j){
            int t = (sqr(nod[j].y-nod[i].y) + sqr(nod[j].x-nod[i].x));
            addedge(i,j,t);
        }
    }
}

int find(int x){
    return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
}
void add(int x,int y){
    int fx = find(x);
    int fy = find(y);
    if (fx != fy){
        fa[fx] = fy;
    }
}

bool cmp(const edge& lhs, const edge& rhs){
    return lhs.flag > rhs.flag;
}
int solve(int bin){
    if (bin == n-1) return 0;
    int ans = 0;
    sort(p,p+num_edge);
    for (int i = 0; i < num_edge; ++i){
        int u = p[i].f;
        int v = p[i].t;
        if (find(u) != find(v)){
            add(u,v);
            ans += p[i].d;
            bin++;
            if (bin == n-1)break;
        }
    }
    return ans;
}

int main(){
    scanf("%d",&T);
    int ks = 0;
    while(T--){
        scanf("%d %d",&n, &q);
        LL ans = 0;
        for (int i = 0; i < 10; ++i)c[i].clear();

        for (int i = 1; i <= q; ++i){
            int x,y;scanf("%d",&x);
            scanf("%d",w+i);
            for (int j = 0; j < x; ++j) {
                scanf("%d",&y);
                c[i].ps(y);
            }
        }
        for (int i = 1; i <= n; ++i){
            nod[i].read();
        }
        if (ks++)puts("");
        DIST();
        sort(p,p+num_edge);

        int bin = 0;
        for (int i = 1; i <= n; ++i)fa[i] = i;

        for (int i = 0; i < num_edge; ++i){
            int u = p[i].f;
            int v = p[i].t;
            if (find(u) != find(v)){
                add(u,v);
                ans += p[i].d;
                p[i].flag = 1;
                bin++;
//                printf(" == %d %d\n",u,v);
                if (bin == n-1)break;
            }
            else p[i].flag = 0;
        }
        sort(p,p+num_edge,cmp);
        num_edge = n-1;


        for (int i = 0; i < (1<<q); ++i){
            int tmp = i;
            LL sum = 0;
            int bin2 = 0;
            for (int j = 1; j <= n; ++j)fa[j] = j;
            for (int j = 0; j < q; ++j){
                int id = q-j;
                if (tmp & 1){
                    for (int k = 1; k < Siz(c[id]); ++k){
                        if (find(c[id][k-1]) != find(c[id][k])){
                            addedge(c[id][k-1],c[id][k],inf);
                            add(c[id][k-1],c[id][k]);
                            bin2++;
                        }
                    }
                    sum += w[id];
                }
                tmp/=2;
            }
            sum += solve(bin2);
            ans = Min(ans,sum);
            num_edge = n-1;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

/**
1

7 3
2 4 1 2
3 3 3 6 7
3 9 2 4 5
0 2
4 0
2 0
4 2
1 3
0 5
4 4

ans = 17

**/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值