题目描述:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=36013
/*
考察点:kruskal算法
算法步骤:
1:在不购买任何子网的情况下,利用kruskal算法求出最小生成树
2:用二进制法枚举所有子网可能的购买方案
3:对每一种子网购买方案,还是利用kruskal算法求出最小生成树。
与第一次用kruskal算法不同的是,这次是基于原图已经求出来的
最小生成树和已经购买的子网二者所包含的边集合来求最小生成树
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 1000 + 5;
const int maxNet = 10;
int cityNum, netNum, pa[maxn], ans, netCost[maxNet];
int x[maxn], y[maxn];
struct Edge {
int u, v, val;
Edge(int u = 0, int v = 0, int val = 0) : u(u), v(v), val(val) {}
bool operator < (const Edge & rhs) const {
return val < rhs.val;
}
};
vector<Edge> all, need;
vector<int> citySub[maxNet]; //citySub[i]表示第i个子网里面的城市编号
void readInput() {
int n, id;
scanf("%d%d", &cityNum, &netNum);
for(int i = 0; i < netNum; i++) {
scanf("%d%d", &n, &netCost[i]);
citySub[i].clear();
while(n--) {
scanf("%d", &id);
citySub[i].push_back(id - 1);
}
}
for(int i = 0; i < cityNum; i++) scanf("%d%d", &x[i], &y[i]);
for(int i = 0; i < cityNum; i++) //先算出所有城市之间所有可能的路径
for(int j = i + 1; j < cityNum; j++) {
int v = (x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j]); //欧几里德距离
all.push_back(Edge(i, j, v)); //将原图所有边存储进去
}
}
int UFind(int u) {
return pa[u] == u ? u : UFind(pa[u]);
}
int kruskal(int cnt, const vector<Edge> &all, vector<Edge> &need) { //kruskal算法,基于所有边(存储在all中),将求出来的最小生成树存储进need中
if(cnt == 1) return 0;
int an = 0;
need.clear();
for(int i = 0; i < all.size(); i++) {
int u = UFind(all[i].u); int v = UFind(all[i].v);
if(u != v) {
pa[u] = v;
an += all[i].val;
need.push_back(all[i]);
if(--cnt == 1) break;
}
}
return an;
}
void binEnum() {
for(int i = 0; i < (1 << netNum); i++) { //这一整个循环就是二进制枚举
for(int i = 0; i < cityNum; i++) pa[i] = i;
int c = 0; int cnt = cityNum;
for(int j = 0; j < netNum; j++)
if(i & (1 << j)) {
c += netCost[j]; //求出购买子网的成本
for(int k = 1; k < citySub[j].size(); k++) { //将该子网内的所有城市连接在一起
int u = UFind(citySub[j][k]); int v = UFind(citySub[j][0]);
if(u != v) {
pa[u] = v;
cnt--;
}
}
}
vector<Edge> vec;
ans = min(ans, c + kruskal(cnt, need, vec)); //每次枚举一种方案就计算最便宜的方案
}
}
int main()
{
//freopen("input.txt", "r", stdin);
int T;
scanf("%d", &T);
while(T--) {
all.clear();
readInput(); //读入数据
for(int i = 0; i < cityNum; i++) pa[i] = i;
sort(all.begin(), all.end());
ans = kruskal(cityNum, all, need); //在不购买任何子网的情况下求出来的最小生成树
binEnum(); //二进制枚举法枚举所有子网购买方案组合,并将其与已经求出来的原图最小生成树
//作为基础,再次求最小生成树。
printf("%d\n", ans);
if(T) printf("\n");
}
return 0;
}