UVA 1151 Buy or Build 最小生成树+二进制选取子集

来源:互联网 发布:安卓优化提速吧 编辑:程序博客网 时间:2024/05/19 13:18

 题目链接:点击打开链接

题意:给你n个点,你的任务是让这n个点连通。为此,你有两种方法,1、在某两点之间建边,费用为两点之间欧几里得距离的平方。2、购买一些套餐,当买了某个套餐后,套餐中的这些点将变得相互连通,问完成任务的最小费用是多少。

思路:先按全都不选套餐,求出此时的最小生成树,记录最小生成树的边,然后用套餐中的边来替换这些边。因为第一次求的那些边,肯定都是最优的,那些边恰好构成一棵最小生成树,不会再有比它小的了,购买套餐之后,结果就是,不仅以前求出的最优边还在里面,还会加入一些更优的边(权值为0)

#include<stdio.h>#include<string.h>#include<algorithm>#include<math.h>using namespace std;int a[10][1010],t[10],cost[10];int f[1010];struct p{    int x;    int y;    int z;} e[500100],vis[1010];bool cmp(p x,p y){    return x.z<y.z;}int getf(int v){    if(f[v]==v)        return v;    f[v]=getf(f[v]);    return f[v];}int merge(int u,int v){    int t1=getf(u);    int t2=getf(v);    if(t1!=t2)    {        f[t1]=t2;        return 1;    }    return 0;}int main(){    int T;    int n,m;    scanf("%d",&T);    while(T--)    {        scanf("%d%d",&n,&m);        for(int i=0; i<m; i++)        {            scanf("%d%d",&t[i],&cost[i]);            for(int j=0; j<t[i]; j++)                scanf("%d",&a[i][j]);        }        int s1[1010],s2[1010];        for(int i=1; i<=n; i++)            scanf("%d%d",&s1[i],&s2[i]);        int sum=0;        for(int i=1; i<=n; i++)        {            for(int j=i+1; j<=n; j++)            {                e[sum].x=i;                e[sum].y=j;                e[sum++].z=(s1[i]-s1[j])*(s1[i]-s1[j])+(s2[i]-s2[j])*(s2[i]-s2[j]);//两条边之间的花费            }        }        sort(e,e+sum,cmp);        int k=0,cnt=0;        for(int i=0; i<=n; i++)            f[i]=i;        int num=0;        for(int i=0; i<sum; i++)        {            if(merge(e[i].x,e[i].y)) //不使用套餐找出最小生成树            {                k+=e[i].z;                cnt++;                vis[num].x=e[i].x;                vis[num].y=e[i].y;                vis[num++].z=e[i].z;//记录使用了呢些边,使用套餐时在已经记录过的这里面选边,避免超时            }            if(cnt==n-1)                break;        }        for(int i=0; i<(1<<m); i++)        {            int k1=0,cnt=0;            for(int j=0; j<=n; j++)                f[j]=j;            for(int j=0; j<m; j++)            {                if(i&(1<<j)) //二进制选子集,考虑用或不用该套餐的每种情况                {                    int u=a[j][0];                    for(int f=1; f<t[j]; f++)                    {                        int v=a[j][f];                        if(merge(u,v))                            cnt++;                    }                  k1+=cost[j];                }            }            for(int j=0;j<num;j++)            {                if(merge(vis[j].x,vis[j].y))                {                    k1+=vis[j].z;                    cnt++;                }               if(cnt==n-1)                    break;            }            k=min(k,k1);        }        printf("%d\n",k);        if(T) printf("\n");    }    return 0;}

0 0