题意:
告诉你n 个点的坐标,你要在两个点之间连线,使得点全部相同,连边的费用为这两个点的欧几里得距离,你的目的是使这个费用最低,并且你有q(q <= 8) 个套餐可以用,一个套餐中会告诉你一些点已经连通, 问最少花费?
思路:
全部的点相通,很明显是最小生成树。
最容易想到的是,暴力枚举哪一个套餐用,哪一个套餐不用,在求最小生成树,这样会超时,因为原图是一个完全图,有100W个边。
有个小优化:
我们可以先求一边最小生成树,n-1个边,在n-1个边中在暴力枚举套餐。 这样边少了很多很多。
暴力枚举套餐,可以用二进制枚举子集的方法。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdlib>
#define Siz(x) (int)x.size()
#define Max(a,b)((a)>(b)?(a):(b))
#define Min(a,b)((a)>(b)?(b):(a))
#define ps push_back
#define sqr(x)((x)*(x))
using namespace std;
const int maxn = 1000 + 7;
const int inf = 0x3f3f3f3f;
typedef long long LL;
int T, n, q, num_edge;
vector<int>c[10];
int w[10],fa[maxn];
//double dist[maxn][maxn];
struct Node{
int x, y;
void read(){
scanf("%d %d",&x, &y);
}
}nod[maxn];
struct edge{
int f,t;
int d;
int flag;
bool operator < (const edge& rhs) const {
return d < rhs.d;
}
}p[maxn*maxn];
void addedge(int x,int y,int d){
p[num_edge].f = x;
p[num_edge].t = y;
p[num_edge].flag = 0;
p[num_edge++].d = d;
}
void DIST(){
num_edge = 0;
for (int i = 1; i <= n; ++i){
for (int j = i+1; j <= n; ++j){
int t = (sqr(nod[j].y-nod[i].y) + sqr(nod[j].x-nod[i].x));
addedge(i,j,t);
}
}
}
int find(int x){
return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
}
void add(int x,int y){
int fx = find(x);
int fy = find(y);
if (fx != fy){
fa[fx] = fy;
}
}
bool cmp(const edge& lhs, const edge& rhs){
return lhs.flag > rhs.flag;
}
int solve(int bin){
if (bin == n-1) return 0;
int ans = 0;
sort(p,p+num_edge);
for (int i = 0; i < num_edge; ++i){
int u = p[i].f;
int v = p[i].t;
if (find(u) != find(v)){
add(u,v);
ans += p[i].d;
bin++;
if (bin == n-1)break;
}
}
return ans;
}
int main(){
scanf("%d",&T);
int ks = 0;
while(T--){
scanf("%d %d",&n, &q);
LL ans = 0;
for (int i = 0; i < 10; ++i)c[i].clear();
for (int i = 1; i <= q; ++i){
int x,y;scanf("%d",&x);
scanf("%d",w+i);
for (int j = 0; j < x; ++j) {
scanf("%d",&y);
c[i].ps(y);
}
}
for (int i = 1; i <= n; ++i){
nod[i].read();
}
if (ks++)puts("");
DIST();
sort(p,p+num_edge);
int bin = 0;
for (int i = 1; i <= n; ++i)fa[i] = i;
for (int i = 0; i < num_edge; ++i){
int u = p[i].f;
int v = p[i].t;
if (find(u) != find(v)){
add(u,v);
ans += p[i].d;
p[i].flag = 1;
bin++;
// printf(" == %d %d\n",u,v);
if (bin == n-1)break;
}
else p[i].flag = 0;
}
sort(p,p+num_edge,cmp);
num_edge = n-1;
for (int i = 0; i < (1<<q); ++i){
int tmp = i;
LL sum = 0;
int bin2 = 0;
for (int j = 1; j <= n; ++j)fa[j] = j;
for (int j = 0; j < q; ++j){
int id = q-j;
if (tmp & 1){
for (int k = 1; k < Siz(c[id]); ++k){
if (find(c[id][k-1]) != find(c[id][k])){
addedge(c[id][k-1],c[id][k],inf);
add(c[id][k-1],c[id][k]);
bin2++;
}
}
sum += w[id];
}
tmp/=2;
}
sum += solve(bin2);
ans = Min(ans,sum);
num_edge = n-1;
}
printf("%lld\n",ans);
}
return 0;
}
/**
1
7 3
2 4 1 2
3 3 3 6 7
3 9 2 4 5
0 2
4 0
2 0
4 2
1 3
0 5
4 4
ans = 17
**/