题意:给n个点和每个点的坐标,q个套餐,要求n个点联通,q个套餐内可以花费c来使得所有套餐内给出的点都联通,否则需要计算点之间的欧几里得距离。这里的欧几里得距离不用开平方。
思路:紫书上的题,先用kruskal求一遍最小生成树,然后枚举所有套餐的情况,每选用一个套餐,把套餐内的点之间的距离变为0(可以直接用并查集把这两个点合起来),维护最小值。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=1005;
int x[maxn],y[maxn],p[maxn];
int q[8][maxn],c[8],t[8];
int n,m,r;
struct Edge{
int u,v,w;
bool operator<(const Edge &a)const{
return w < a.w;
}
} edge[maxn*maxn],re_edge[maxn];
int dist(int x1,int y1,int x2,int y2){
return (x1 - x2)*(x1 - x2) + (y1-y2)*(y1-y2);
}
int find(int x){
return p[x]==x?x:p[x]=find(p[x]);
}
ll Kruskal(int n,int m)
{
sort(edge,edge+m);
for(int i =1; i<=n; i++) p[i]=i;
int cnt = 0;
ll ans = 0;
for(int i=0; i < m; i++)
{
int x=find(edge[i].u),y=find(edge[i].v);
if(x!=y){
re_edge[cnt++]=edge[i];
ans+=edge[i].w;
p[x]=y;
}
if(cnt == n -1) break;
}
if(cnt < n-1) ans = -1;
return ans;
}
ll re_Kruskal()
{
ll ans=0;
for(int i=0; i < n-1; i++){
int x=find(re_edge[i].u),y=find(re_edge[i].v);
if(x!=y){
ans+=re_edge[i].w;
p[x]=y;
}
}
return ans;
}
int main()
{
//freopen("in.txt","r",stdin);
int T;
scanf("%d",&T);
while(T--)
{
m = 0;
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]);
for(int i=1; i<=n; i++){
for(int j=i+1; j<=n; j++){
edge[m].u = i;
edge[m].v = j;
edge[m++].w = dist(x[i],y[i],x[j],y[j]);
}
}
ll ans= Kruskal(n,m);//先找出最小生成树
for(int s=0; s<(1<<r); s++)
{
ll tans=0;
for(int i=1; i<=n; i++) p[i]=i;
for(int i=0; i<r; i++){
if((s >> i)&1)
{
tans+=c[i];
for(int j=2; j<=t[i]; j++)
p[find(q[i][j-1])]=find(q[i][j]);
}
}
tans+=re_Kruskal();
ans=min(ans,tans);
}
printf("%lld\n",ans);
if(T) printf("\n");
}
return 0;
}