先求一次最小生成树,可以排除n*(n*1)/2-(n-1)条边,每次利用二进制法枚举套餐的选择,套餐中的点直接处理,如果两个套餐有公共点直接合并,他们一定连通,然后枚举第一步最小生成树得到的n-1条边就能够得到在购买当前套餐下能得到的最优解。
注意:修建两点之间的道路的费用是欧几里德距离的平方。
AC代码:
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn=1000+5;
const int inf=1<<30;
int n,q,cnt;
int px[maxn],py[maxn],p[maxn],vis[maxn];
struct node{
int a,b;
int w;
bool operator <(const node &p)const {
return w<p.w;
}
}e[maxn*maxn/2],ex[maxn];
struct net{
int cnt;
int point[maxn];
int w;
}c[10];
inline int find(int x){
return p[x]==x?x:find(p[x]);
}
int kru(int h){
int ans=0;
for(int i=0;i<n;++i) p[i]=i;
memset(vis,0,sizeof(vis));
for(int i=0;i<q;++i){
if((h>>i)&1){
ans+=c[i].w;
int k=c[i].point[0];
for(int j=0;j<c[i].cnt;++j){
if(vis[c[i].point[j]]) {
k=find(c[i].point[j]);
break;
}
}
for(int j=0;j<c[i].cnt;++j){
if(!vis[c[i].point[j]]) p[c[i].point[j]]=k;
vis[c[i].point[j]]=1;
}
}
}
for(int i=0;i<n-1;++i){
int x=find(ex[i].a), y=find(ex[i].b);
if(x!=y){
ans+=ex[i].w;
p[y]=x;
}
}
return ans;
}
int solve(){
int ans=inf;
sort(e,e+cnt);
for(int i=0;i<n;++i) p[i]=i;
int c2=0;
for(int i=0;i<cnt;++i){
int x=find(e[i].a), y=find(e[i].b);
if(x!=y){
p[y]=x;
ex[c2++]=e[i];
}
}
for(int i=0;i<(1<<q);++i){
ans=min(ans,kru(i));
}
return ans;
}
int main(){
int T;
scanf("%d",&T);
int kase=0;
while(T--){
if(kase++) printf("\n");
scanf("%d%d",&n,&q);
for(int i=0;i<q;++i){
scanf("%d%d",&c[i].cnt,&c[i].w);
for(int j=0;j<c[i].cnt;++j) {
scanf("%d",&c[i].point[j]);
c[i].point[j]--;
}
}
for(int i=0;i<n;++i) scanf("%d%d",&px[i],&py[i]);
cnt=0;
for(int i=0;i<n;++i)
for(int j=i+1;j<n;++j){
e[cnt].a=i; e[cnt].b=j;
e[cnt++].w=(px[i]-px[j])*(px[i]-px[j])+(py[i]-py[j])*(py[i]-py[j]);
}
printf("%d\n",solve());
}
return 0;
}
如有不当之处欢迎指出!