11.2.2-uva1151-kruskal算法综合运用,贪心

#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;
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值