题目链接:点击打开链接
题意:给你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;
}