【最小生成树+子集枚举】Uva1151 Buy or Build

时间:2023-03-08 23:31:28
【最小生成树+子集枚举】Uva1151 Buy or Build

Description

平面上有n个点(1<=N<=1000),你的任务是让所有n个点连通,为此,你可以新建一些边,费用等于两个端点的欧几里得距离的平方。

另外还有q(0<=q<=8)个套餐,可以购买,如果你购买了第i个套餐,该套餐中的所有结点将变得相互连通,第i个套餐的花费为ci。

求最小花费。

Solution

对于套餐可以用子集枚举处理,求最小生成树时只需考虑原图是最小生成树中的边。

正确性可以按Kruskal过程,以前被舍弃的边选了套餐后依然会被舍弃。

Code

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=; int x[maxn],y[maxn],p[maxn];
int find(int x){return p[x]==x?x:p[x]=find(p[x]);}
struct edge{
int u,v,w;
bool operator<(const edge&a)
const {return w<a.w;}
}_e[maxn*maxn],e[maxn];
int dist(int a,int b){
return (x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]);
}
int q[][maxn],c[],t[];
int n,m,r,cnt; void clear(){
m=cnt=;
} ll solve(){
ll ret=;
for(int i=;i<n;i++){
int x=find(e[i].u),y=find(e[i].v);
if(x!=y){
ret+=e[i].w;
p[x]=y;
}
}
return ret;
} int main(){
int T;
scanf("%d",&T); while(T--){
clear();
scanf("%d%d",&n,&r);
for(int i=;i<r;i++){
scanf("%d%d",&t[i],&c[i]);
for(int j=;j<=t[i];j++)
scanf("%d",&q[i][j]);
} for(int i=;i<=n;i++)
scanf("%d%d",&x[i],&y[i]),p[i]=i; for(int i=;i<=n;i++)
for(int j=i+;j<=n;j++)
_e[++m]=(edge){i,j,dist(i,j)};
sort(_e+,_e+m+); ll ans=;
for(int i=;i<=m;i++){
int x=find(_e[i].u),y=find(_e[i].v);
if(x!=y){
e[++cnt]=_e[i];
ans+=_e[i].w;
p[x]=y;
}
} for(int S=;S<(<<r);S++){
ll ansx=;
for(int i=;i<=n;i++) p[i]=i; for(int i=;i<r;i++)
if(S&(<<i)){
ansx+=c[i];
for(int j=;j<=t[i];j++)
p[find(q[i][j-])]=find(q[i][j]);
}
ansx+=solve();
ans=min(ans,ansx);
}
printf("%lld\n",ans);
if(T) printf("\n");
}
return ;
}