这是一道最小生成树的题,对理解Kruskal算法很有好处。
数据较小,直接考虑枚举购买“子网络”的情况。
采用Kruskal算法,注意每种情况都要重新初始化并查集。
在购买子网络时就将各个子网络unite()(不同的子网络不需要!)。
复杂度O(2^q * N^2 * lgN)(边有n*(n-1)/2条)
(计算发现最大的时间数量级是1e8,用了190ms)
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> #include <vector> using namespace std; const int N = 1e3; const int INF = 0x3f3f3f3f; int par[N + 1], ran[N + 1], dx[N + 1], dy[N + 1]; vector<int> vec[10]; int qw[10]; struct edge { int x, y, cost; } es[N * N + 1]; bool cmp(edge x1, edge x2) { return x1.cost < x2.cost; }; void init(int x) { for (int i = 0; i <= x; i++) { par[i] = i; ran[i] = 0; } } int find(int x) { if (par[x] == x) return x; return par[x] = find(par[x]); } void unite(int x, int y) { x = find(x); y = find(y); if (x == y) return; if (ran[x] < ran[y]) par[x] = y; else { par[y] = x; if (ran[x] == ran[y]) ran[x]++; } } bool same(int x, int y) { return find(x) == find(y); } int dis(int x, int y) { return (dx[x] - dx[y]) * (dx[x] - dx[y]) + (dy[x] - dy[y]) * (dy[x] - dy[y]); } int main() { int t, n, q, x, y; scanf("%d", &t); while (t--) { //注意读入格式 getchar(); scanf("%d%d", &n, &q); for (int i = 0; i < q; i++) vector<int>().swap(vec[i]); for (int i = 0; i < q; i++) { //读入子网络的信息 scanf("%d%d", &x, &qw[i]); for (int j = 0; j < x; j++) { scanf("%d", &y); vec[i].push_back(y); } } for (int i = 1; i <= n; i++) { scanf("%d%d", &dx[i], &dy[i]); } int l = 0; for (int i = 1; i <= n; i++) { for (int j = i + 1; j <= n; j++) { //计算所有点与点之间的边, j=i+1可以省去重复的边 es[l++] = {i, j, dis(i, j)}; } } sort(es, es + l, cmp); int res = INF; for (int i = 0; i < (1 << q); i++) { //穷举所有购买子网络的方案 init(n); int tres = 0; //每次都要初始化 for (int j = 0; j < q; j++) { if ((i >> j) & 1) { for (int k = 0; k < vec[j].size() - 1; k++) { unite(vec[j][k], vec[j][k + 1]); } tres += qw[j]; } } // Kruskal算法 for (int j = 0; j < l; j++) { if (!same(es[j].x, es[j].y)) { unite(es[j].x, es[j].y); tres += es[j].cost; } } res = min(res, tres); } //注意输出格式 printf("%d\n", res); if (t) printf("\n"); } }