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

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

分析:先求一次原图的最小生成树,得到n-1条边,然后枚举每个套餐的时候光考虑着n-1条边,套餐中的点的权可以之间看作零。

算法正确性的证明:买了套餐之后,相当于一些边的权变为零,而对于不在套餐中的边e,排序在e之前的边一个都没少,反而会多了一些权为零的边,所有在原图Kruskal时被仍掉的边在后面的Kruskal中也一样会被扔掉。

注意:使用二进制枚举

代码:

#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <cstdio>
#include <vector>
#include <iomanip>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <sstream>
#define ll long long
#define mod 1000000007
#define mod1 10001
#define mem(a) memset(a,0,sizeof(a))

using namespace std;

const int maxn = 1000 + 5 , inf = 0x3f3f3f3f ;
//x,y存坐标,t存每个套餐中点的个数,c存每个套餐的价格,q存每个套餐中的点,f为并查集
int n,r,x[maxn],y[maxn],t[9],c[9],q[9][maxn],f[maxn];
//计算欧几里得距离
int dist(int x1,int y1,int x2,int y2){return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);}

struct Edge{
    int u,v,w;
    Edge(int a,int b,int c):u(a),v(b),w(c){}
    bool operator < (const Edge &rhs) const {
        return w < rhs.w;
    }
};

int Find(int u){return f[u]==u?u:f[u]=Find(f[u]);}

vector<Edge>edges;
vector<Edge>e;

void Init(){
    mem(x),mem(y),mem(f),mem(q),mem(t);mem(c);
    edges.clear();
    e.clear();
}
//计算枚举套餐时的最小生成树
int solve(){
    ll res = 0;
    for(int i=0;i<e.size();i++){
        int u = Find(e[i].u);
        int v = Find(e[i].v);
        if(u!=v){
            res+=e[i].w;
            f[u]=v;
        }
    }
    return res;
}

int main(){
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int T;
    scanf("%d",&T);
    while(T--){

        Init();
        scanf("%d%d",&n,&r);
        for(int i=0;i<r;i++){
            scanf("%d%d",&t[i],&c[i]);
            for(int j=1;j<=t[i];j++){
                scanf("%d",&q[i][j]);
            }
        }

        for(int i=1;i<=n;i++)
            scanf("%d%d",&x[i],&y[i]),f[i]=i;
        for(int i=1;i<=n;i++){
            for(int j=i+1;j<=n;j++){
                edges.push_back(Edge(i,j,dist(x[i],y[i],x[j],y[j])));
            }
        }
        //求原图的最小生成树
        sort(edges.begin(),edges.end());
        ll ans = 0;
        for(int i=0;i<edges.size();i++){
            int u = Find(edges[i].u);
            int v = Find(edges[i].v);
            if(u!=v){
                e.push_back(edges[i]);
                ans+=edges[i].w;
                f[u]=v;
            }
        }
        //二进制枚举
        for(int S=0;S<(1<<r);S++){
            ll ansx=0;
            for(int i=1;i<=n;i++) f[i]=i;
            for(int i=0;i<r;i++){
                if(S&(1<<i)){//判断每个套餐是否被枚举
                    ansx+=c[i];
                    for(int j=2;j<=t[i];j++){
                        //这里套餐里的点的距离都为0,换个思路其实就是把他们放在一个集合里
                        f[Find(q[i][j-1])] = Find(q[i][j]);
                    }
                }
            }
            ansx+=solve();
            ans=min(ans,ansx);//不断更新结果
        }
        printf("%lld\n",ans);
        if(T) cout<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/insist_77/article/details/80943479
今日推荐