#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=1000+10;
int p[maxn],x[maxn],y[maxn]; //x于y表示点的坐标
int n;//点的个数
vector<int> subn[10];//用vector作容器存储套餐数组 每个数组包含该套餐所包含的点
int cost[10];//每个套餐的价钱
//并查集模板 使用前根据情况初始化
int find(int x){
return x==p[x]?x:p[x]=find(p[x]);
}
//int cmp(int a,int b){return w[a]<w[b];}
//边的模板
struct Edge{
int u,v,d;
Edge(int u,int v,int d):u(u),v(v),d(d){
}
bool operator < (const Edge& rhs) const {
return d<rhs.d;}
};
//Kruskal算法 使用前需要对边排序 初始化并查集 形参cnt表示边的集合e孤立点的个数 可删除 只会稍微增加运算量 used容器用来存放生成树的边
int Kruskal(int cnt,vector<Edge>& e,vector<Edge>& used){
if(cnt==1) return 0; // cnt连通区域个数 连成一块 返回0
int ans=0;//总价值
used.clear();//容器初始化
//不能初始化并查集 (套餐已经初始化)
for(int i=0;i<e.size();i++){
int u=find(e[i].u),v=find(e[i].v);
if(u!=v) {
p[u]=v;
ans+=e[i].d;
used.push_back(e[i]);//存储边
if(--cnt==1) break; //已经连接成一个点 停止
}
}
return ans;
}
int main(){
int T,q;
//T总情况数 q套餐个数
cin>>T;
while(T--){
cin>>n>>q;
//套餐的输入
for(int i=0;i<q;i++){
int cnt;//某套餐i包含的点个数
cin>>cnt>>cost[i];
subn[i].clear();//初始化
while(cnt--){
int X;
cin>>X;
subn[i].push_back(X-1);//值减一从零开始
}
}
//点的坐标输入
for(int i=0;i<n;i++)
cin>>x[i]>>y[i];
//边的输入 计算各点之间距离 并将所有边放入容器
vector<Edge> e,need;
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++){
int c=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
e.push_back(Edge(i,j,c));
}
//边的排序
sort(e.begin(),e.end());
//并查集初始化并使用Kruskal算法
for(int i=0;i<n;i++)//
p[i]=i;
int ans=Kruskal(n,e,need);
//枚举套餐组合 每一种情况下 用并查集的合并先连接各套餐的点 之后在用上面生成的最小生成树的边(即need的边) 连接剩余点 利用了贪心法
for(int mask=0;mask<(1<<q);mask++){//1<<q 2的q次方 枚举套餐的组合
for(int i=0;i<n;i++) p[i]=i; //并查集初始化
int cnt=n,c=0; //c 方案的总套餐费 cnt依然是区域个数 可删除
for(int i=0;i<q;i++) if(mask&(1<<i)){ //用二进制方法判断组合是否包含套餐i
c+=cost[i];
//讲该套餐的点连接 每连接一次cnt--
for(int j=1;j<subn[i].size();j++){
int u=find(subn[i][j]),v=find(subn[i][0]);
if(u!=v){
p[u]=v; cnt--;
}
}
}
vector<Edge> dummy; //仅用来为下面凑参数 无实际意义
ans=min(ans,c+Kruskal(cnt,need,dummy));
}
cout<<ans<<endl;
if(T) cout<<endl;
}
//system("pause");
return 0;
}
去除部分cnt 简化版 该题情况稍复杂些 用了大量vector容器 使用时一定要记得初始化
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=1000+10;
int p[maxn],x[maxn],y[maxn]; //x于y表示点的坐标
int n;//点的个数
vector<int> subn[10];//用vector作容器存储套餐数组 每个数组包含该套餐所包含的点
int cost[10];//每个套餐的价钱
//并查集模板 使用前根据情况初始化
int find(int x){
return x==p[x]?x:p[x]=find(p[x]);
}
//int cmp(int a,int b){return w[a]<w[b];}
//边的模板
struct Edge{
int u,v,d;
Edge(int u,int v,int d):u(u),v(v),d(d){
}
bool operator < (const Edge& rhs) const {
return d<rhs.d;}
};
//Kruskal算法 使用前需要对边排序 初始化并查集 形参cnt表示边的集合e孤立点的个数 used容器用来存放生成树的边
int Kruskal(vector<Edge>& e,vector<Edge>& used){
int ans=0;//总价值
used.clear();//容器初始化
//不能初始化并查集 (套餐已经初始化)
for(int i=0;i<e.size();i++){
int u=find(e[i].u),v=find(e[i].v);
if(u!=v) {
p[u]=v;
ans+=e[i].d;
used.push_back(e[i]);//存储边
//已经连接成一个点 停止
}
}
return ans;
}
int main(){
int T,q;
//T总情况数 q套餐个数
cin>>T;
while(T--){
cin>>n>>q;
//套餐的输入
for(int i=0;i<q;i++){
int cnt;//某套餐i包含的点个数
cin>>cnt>>cost[i];
subn[i].clear();//初始化
while(cnt--){
int X;
cin>>X;
subn[i].push_back(X-1);//值减一从零开始
}
}
//点的坐标输入
for(int i=0;i<n;i++)
cin>>x[i]>>y[i];
//边的输入 计算各点之间距离 并将所有边放入容器
vector<Edge> e,need;
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++){
int c=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
e.push_back(Edge(i,j,c));
}
//边的排序
sort(e.begin(),e.end());
//并查集初始化并使用Kruskal算法
for(int i=0;i<n;i++)//
p[i]=i;
int ans=Kruskal(e,need);
//枚举套餐组合 每一种情况下 用并查集的合并先连接各套餐的点 之后在用上面生成的最小生成树的边(即need的边) 连接剩余点 利用了贪心法
for(int mask=0;mask<(1<<q);mask++){//1<<q 2的q次方 枚举套餐的组合
for(int i=0;i<n;i++) p[i]=i; //并查集初始化
int c=0; //c 方案的总套餐费
for(int i=0;i<q;i++) if(mask&(1<<i)){ //用二进制方法判断组合是否包含套餐i
c+=cost[i];
//讲该套餐的点连接 每连接一次cnt--
for(int j=1;j<subn[i].size();j++){
int u=find(subn[i][j]),v=find(subn[i][0]);
if(u!=v){
p[u]=v;
}
}
}
vector<Edge> dummy; //仅用来为下面凑参数 无实际意义
ans=min(ans,c+Kruskal(need,dummy));
}
cout<<ans<<endl;
if(T) cout<<endl;
}
//system("pause");
return 0;
}