先求一次最小生成树,可以排除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; }
如有不当之处欢迎指出!