1. 程式人生 > >UVA 1151 Buy or Build 最小生成樹+二進位制選取子集

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;
}