平面上有\(n(n<=1000)\)个点,你的任务是让全部n个点联通。为此,你能够新建一些边,费用等于两个端点的欧几里得距离平方。另外还有\(q(q<=8)\)个套餐能够购买,若是你购买了第\(i\)个套餐,该套餐中的全部结点将变得相互链接。第\(i\)个套餐的花费为\(C_i\)。c++
\(Kruskal\)算法
最容易想到的算法是:先枚举购买哪些套餐,把套餐中包含的权值设为\(0\),而后求最小生成树。因为枚举量为\(O(2^q)\),给边排序的时间复杂度为\(O(n^2logn)\),而排序以后每次\(kruskal\)算法的时间复杂度为\(O(n^2)\),所以总时间复杂度为\(O(2^qn^2+n^2logn)\),对于题目来讲的规模太大了。优化
只需一个小小的优化便可下降时间复杂度:先求一次原图 (不购买任何套餐) 的最小生成树,获得\(n-1\)条边,其他的边就没用了。而后枚举买哪些套餐(这里能够用状态压缩的思想),则枚举套餐后再求最小生成树时,图上的边已经寥寥无几。spa
为何能够这样呢? 大部分题解都没有证实。这里给出证实过程code
首先回顾一下,在\(kruskal\)算法中,哪些边不会进入最小生成树。答案是:两端已经属于同一个集合的边。买了套餐后,至关于一些边的边权变成了\(0\),而对于不在套餐中的每条边\(e\),排序在\(e\)以前的边一个都没少,反而还多了一些权值为\(0\)的边,因此在原图\(kruskal\)时被“扔掉”的边,在后面的枚举套餐中也同样会被扔掉。排序
#include<bits/stdc++.h> using namespace std; const int MAXN=1000+10; const int MAXM=MAXN*MAXN; int n,q,T,ans=0x3f3f3f3f; int s[10][MAXN]; int c[10]; struct Node2 { int x,y; }city[MAXN]; struct Node { int u,v,w; }edge[MAXM],g[MAXM]; int cnt,m; int fa[MAXN]; int save[MAXN]; inline int read() { int tot=0; char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') { tot=tot*10+c-'0'; c=getchar(); } return tot; } inline bool cmp(Node x,Node y) { return x.w<y.w; } inline int find(int k) { if(fa[k]==k)return k; else return fa[k]=find(fa[k]); } inline int init_kruskal() { int tot=0,cc=0; for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=cnt;i++) { if(fa[find(edge[i].u)]!=fa[find(edge[i].v)]) { fa[find(edge[i].u)]=find(edge[i].v); tot++; cc+=edge[i].w; save[tot]=i;//记录边 } if(tot==n-1)break; } return cc; } inline int kruskal(int tot) { int cc=0,t=tot; for(int i=1;i<n;i++) { if(find(g[i].u)!=find(g[i].v)) { fa[find(g[i].u)]=find(g[i].v); t++; cc+=g[i].w; } if(t==n-1)break; } return cc; } inline void solve() { for(int ss=0;ss<(1<<q);ss++)//状压思想,用二进制来表示选仍是不选 { for(int i=1;i<=n;i++) fa[i]=i;//初始化并查集 int tot=0;//选中套餐中被链接的点数 int cc=0;//套餐的钱 for(int k=1;k<=q;k++) { if(ss&(1<<(k-1)))//如该套餐被选中 { //cout<<k<<" "; cc+=c[k]; for(int i=1;i<=s[k][0];i++) { for(int j=i+1;j<=s[k][0];j++) { //cout<<s[k][0]<<" "<<k<<" "<<s[k][i]<<" "<<s[k][j]<<endl; if(find(s[k][i])!=find(s[k][j])) { fa[find(s[k][i])]=find(s[k][j]); tot++; } } } } } //cout<<endl; //cout<<cc<<endl; //cout<<tot<<" "<<kruskal(tot)<<" "<<cc<<endl; ans=min(ans,kruskal(tot)+cc);//更新最小值 } } int main() { T=read(); while(T--) { cnt=0; n=read();q=read(); for(int i=1;i<=q;i++) { s[i][0]=read();c[i]=read(); for(int j=1;j<=s[i][0];j++) s[i][j]=read();//读入套餐 } for(int i=1;i<=n;i++) city[i].x=read(),city[i].y=read(); for(int i=1;i<=n;i++) { for(int j=i+1;j<=n;j++) { edge[++cnt].u=i; edge[cnt].v=j; edge[cnt].w=(city[i].x-city[j].x)*(city[i].x-city[j].x)+(city[i].y-city[j].y)*(city[i].y-city[j].y); } } sort(edge+1,edge+1+cnt,cmp); ans=init_kruskal();//原始图的最小生成树 //cout<<ans<<endl; for(int i=1;i<n;i++) { g[i].u=edge[save[i]].u; g[i].v=edge[save[i]].v; g[i].w=edge[save[i]].w; }//建一个新图 /*for(int i=1;i<n;i++) { cout<<g[i].u<<" "<<g[i].v<<" "<<g[i].w<<endl; } cout<<endl;*/ solve();//准备枚举 cout<<ans<<endl; if(T)cout<<endl;//UVA不会省略最后的换行符 } return 0; }