题意:
在平面上有n个点,要让所有n个点都连通,所以你要构造一些边来连通他们,连通的费用等于两个端点的欧几里得距离的平方。另外还可以选择q个套餐,可以购买,套餐内的所有点将会被连通。求最小花费。
题解:
如果不选择套餐的话,就是裸的MST。现在有了套餐可以选择,可以选择多个,那么就把它们(套餐里的点)先加入到生成树里,用位运算枚举集合的子集。
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #include<cstring> using namespace std; const int maxn=1005,INF=0x3f3f3f3f; int n,cnt; int par[maxn]; struct edge { int u,v,cost; bool operator <(const edge& a) const//重载<运算符 { return cost<a.cost; } }es[maxn*maxn]; struct point { int x,y; }p[maxn]; struct buy { int num;//套餐的点的数量 int cost;//套餐的花费 int a[maxn];//套餐里的点 }bu[8]; void init() { for(int i=1;i<=n;i++) par[i]=i; } int find(int x) { return x==par[x]?x:par[x]=find(par[x]); } void unite(int x,int y) { x=find(x); y=find(y); if(x!=y) par[x]=y; } bool same(int x,int y) { return find(x)==find(y); } int dis(point a,point b) { return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); } int kruskal() { int res=0; for(int i=0;i<cnt;i++) { edge e=es[i]; if(!same(e.u,e.v)) { res+=e.cost; unite(e.u,e.v); } } return res; } int main() { int t; cin>>t; while(t--) { int q; cin>>n>>q; for(int i=0;i<q;i++) { cin>>bu[i].num>>bu[i].cost; for(int j=0;j<bu[i].num;j++) cin>>bu[i].a[j]; } cnt=0; for(int i=1;i<=n;i++) { cin>>p[i].x>>p[i].y; } for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++) { es[cnt].u=i; es[cnt].v=j; es[cnt++].cost=dis(p[i],p[j]); } sort(es,es+cnt); int ans=INF; for(int i=0;i<(1<<q);i++)//枚举子集 { init();//每次选择套餐的时候,对并查集初始化 int temp=i; int mst=0; for(int j=0;j<q;j++) { if(temp&1) { mst+=bu[j].cost; for(int k=1;k<bu[j].num;k++) { unite(bu[j].a[k],bu[j].a[0]); } } temp>>=1; } mst+=kruskal(); ans=min(ans,mst); } cout<<ans<<endl; if(t) cout<<endl; } return 0; }