Uva1151.Buy or Build (最小生成树)

Uva1151.Buy or Build (最小生成树)

Description

给出n个点 (n1000) 个点,以及点的坐标,两点之间连通的代价为其距离的平方。现在还有q种购买方案 (q8) ,每一种购买方案的花费为 ci ,购买后就会使得其所包含的点连通。

现在想要求出花费最少,使得图上的点全部连通,输出其最小花费,注意各组输出之间有一个空行

Solution

显然暴力方法不可取。

考虑最小生成树的算法:

对一个图来说,如果在其最小生成树上加一些边,可能会改变原图的最小生成树。原因十分简单,边的个数会超过 n1 ,形成一些环。如果对每个环来说,我们去掉那些权值较大的边,生下来的就是现在图的最小生成树。

对于本题而言,购买套餐,就等价于在原来的最小生成树上加了一些权值为0的边,如此一来,只需要对原来的最小生成树+新加入权值为0的边再跑一边克鲁斯卡尔算法即可。

对于不同的套餐选择,可以采用二进制枚举的方法。

Code

#include<bits/stdc++.h>
#define ll long long
#define nmax 600000
#define pmax 1005
using namespace std;
struct edge{
    int f,t,w;
}e[nmax],_e[pmax];
struct point{
    int x,y;
}p[pmax];
int cost[10],num[10],qq[10][pmax];
int fa[nmax];
int t,n,q,cnt=0,cnt2 = 0,ans = 1000000000,costemp;
void makeset(int x){
    fa[x] = x;
}
int findset(int x){
    int rt = x,temp;
    while(fa[rt] != rt) rt = fa[rt];
    while(x != rt){
        temp = fa[x];
        fa[x] = rt;
        x = temp;
    }
    return rt;
}
int unionset(int x, int y){
    x = findset(x);
    y = findset(y);
    if(x == y) return -1;
    else{
        fa[x] = y;
        return 0;
    }
}
bool cmp(edge a, edge b){
    return a.w < b.w;
}
int dis(point a , point b){
    return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
void caledge(){

    for(int i = 1;i<=n-1;++i){
        for(int j = i+1;j<=n;++j){
            e[++cnt].f = i;
            e[cnt].t = j;
            e[cnt].w = dis(p[i],p[j]);
        }
    }
}
void solve(){
    for(int i = 1;i<cnt2;++i){
        if(unionset(_e[i].f,_e[i].t) !=-1){
            costemp += _e[i].w;
        }
    }
}
int main(){
//    freopen("out.txt","w",stdout);
    scanf("%d",&t);
    while(t--){
        cnt = 0;
        ans = 0;
        cnt2 = 1;
        scanf("%d %d",&n,&q);
        for(int i = 1;i<=q;++i){
            scanf("%d %d",&num[i],&cost[i]);
            for(int j = 1;j<=num[i];++j) scanf("%d",&qq[i][j]);
        }
        for(int i = 1;i<=n;++i){
            int x, y;
            scanf("%d %d",&x,&y);
            p[i].x = x; p[i].y = y;
        }
        caledge();
        sort(e+1,e+1+cnt,cmp);
        for(int i = 1;i<=n;++i) makeset(i);
        for(int i = 1;i<=cnt;++i){
            if(unionset(e[i].f,e[i].t) != -1){
                ans+=e[i].w;
                _e[cnt2++] = e[i];
            }
        }
        for(int s = 0;s<(1<<q);++s){
            for(int i = 1;i<=n;++i) makeset(i);
            costemp = 0;
            for(int j = 0;j<q;++j){
                if(1&(s>>j)){ // buy the gift :D
                    costemp += cost[j+1];
                    for(int k = 1;k<=num[j+1]-1;++k){
                        unionset(qq[j+1][k],qq[j+1][k+1]);
                    }
                }
            }
            solve();
            ans = min(ans,costemp);
        }
        printf("%d\n",ans);
        if(t>0) printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值