题意:给定n个点,你的任务是让它们都连通。你可以新建一些边,费用等于两点距离的平方(当然越小越好),另外还有几种“套餐”,可以购买,你购买的话,那么有些边就可以连接起来,
每个“套餐”,也是要花费的,让你求出最少花费。
析:首先想到的是把所有情况都考虑算一下,然后找出最少的,先算没有“套餐”的,然后算有的,用二进制枚举的话,总时间复杂度为O(2qn2+n2logn),这个时间复杂度太大了吧,肯定会超时,
那么我们就可以优化一下,首先先算出来最小生成树,并且把每条边都保存下来,那么加了“套餐”之后,就不用全部枚举了,这是一个优化,然后在买“套餐”后,那么有的权值就变成了0,
这个也不要加上,再重新枚举,我们在之前就把它们连接上就OK。其他的和最小生成树一样。
代码如下:
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; const int maxn = 1000 + 10; struct node{ int u, v, w; bool operator < (const node &p) const { return w < p.w; } }; node a[maxn*maxn/2]; int p[maxn], x[maxn], y[maxn], n, m, q[8][maxn], c[2][10], indx, l[maxn]; int Find(int x){ return x == p[x] ? x : p[x] = Find(p[x]); } int Kruskal(){//最小生成树 int ans = 0; l[0] = 0; for(int i = 0; i < indx; ++i){ int x = Find(a[i].u); int y = Find(a[i].v); if(x != y){ ans += a[i].w; p[x] = y; l[++l[0]] = i;//把最小生成树的边都保存下来 } } return ans; } int Kruskal2(){//买“套餐”后的最小生成树 int ans = 0; for(int i = 1; i <= l[0]; ++i){ int ii = l[i]; int x = Find(a[ii].u); int y = Find(a[ii].v); if(x != y){ p[x] = y; ans += a[ii].w; } } return ans; } int main(){ // freopen("int.txt", "r", stdin); int T; cin >> T; while(T--){ scanf("%d %d", &n, &m); for(int i = 0; i < m; ++i){ scanf("%d %d", &c[0][i], &c[1][i]); for(int j = 0; j < c[0][i]; ++j) scanf("%d", &q[i][j]); } for(int i = 0; i < n; ++i) scanf("%d %d", &x[i], &y[i]); indx = 0; for(int i = 0; i < n; ++i)//计算权植 for(int j = i+1; j < n; ++j){ a[indx].u = i+1; a[indx].v = j+1; a[indx++].w = (x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j]); } sort(a, a+indx); for(int i = 0; i <= n; ++i) p[i] = i; int ans = Kruskal();//计算不买”套餐“时的最小费用 for(int i = 0; i < (1<<m); ++i){//二进制法枚举 int cost = 0; for(int j = 0; j <= n; ++j) p[j] = j; for(int j = 0; j < m; ++j){ if(i&(1<<j)){ cost += c[1][j]; for(int k = 0; k < c[0][j]-1; ++k){ int xx = Find(q[j][k]); int yy = Find(q[j][k+1]); if(xx != yy) p[xx] = yy; } } } ans = min(ans, cost + Kruskal2());//加上买套餐的费用,更新最小值 } printf("%d ", ans); if(T) printf(" "); } return 0; }