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

 题目链接:点击打开链接

题意:给你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;
}

猜你喜欢

转载自blog.csdn.net/zch3210/article/details/70327719
今日推荐