uva1151:最小生成树+状态枚举

uva1151

题意:被题目和图给吓到了,直接上这一题很简单。就是让所有城市都连通,有两种方案,一种是两个点之间修一条边,价格为两点距离的平方。另一种方案是买一个套餐,套餐里的城市都连通。求最下的花费。

题解:二进制状态压缩,枚举套餐,然后再并查集找最小生成树。和HDU1102很像,已经构造的边就合并,让后再查找剩下的。一次AC,很舒服~~

代码:

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <cmath>
#include <iostream>
using namespace std;
typedef pair<int,int>pii;
int const N = 1000 + 10;
int const M = 1000000;
int const inf = 0x7f7f7f7f;
int n,m,q,cost[10],fa[N];
vector<int>G[10];
pii a[N];
struct Edge
{
	int u,v,w;
}p[M];
bool cmp(Edge a,Edge b){
	return a.w < b.w;
}
int Distance(int x,int y){
	return (a[x].first-a[y].first)*(a[x].first-a[y].first)+(a[x].second-a[y].second)*(a[x].second-a[y].second);
}
void Init(){
	for(int i=0;i<10;i++)	G[i].clear();
	scanf("%d%d",&n,&q);
	for(int i=0;i<q;i++){  //记录套餐
		int cnt;
		scanf("%d%d",&cnt,&cost[i]);   //cost记录套餐的价格
		for(int j=0;j<cnt;j++){
			int tmp;
			scanf("%d",&tmp);
			G[i].push_back(tmp);   //记录套餐的城市
		}
	}
	for(int i=1;i<=n;i++){  //记录坐标
		int x,y;
		scanf("%d%d",&x,&y);
		a[i] = make_pair(x,y);
	}
	m = 0;
	for(int i=1;i<=n;i++)  //记录任意两点之间的记录
		for(int j=i+1;j<=n;j++)
			p[++m] = (Edge){i,j,Distance(i,j)};
}
int find(int x){
	return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
void Union(int x,int y){
	int fx = find(x),	fy = find(y);
	if(fx != fy)	fa[fx] = fy;
}
void updata(int k){   //加入第k个套餐
	for(int i=0;i<G[k].size();i++)
		for(int j=i+1;j<G[k].size();j++)
			Union(G[k][i],G[k][j]);
		
}
int Kruskal(){
	int sum = 0;
	for(int i=1;i<=m;i++){
		int fx = find(p[i].u),	fy = find(p[i].v);
		if(fx != fy){
			fa[fx] = fy;
			sum += p[i].w;
		}
	}
	return sum;
}
int main(){
	int T;
	scanf("%d",&T);
	bool flag = false;
	while(T--){
		if(flag){
			printf("\n");
			flag = false;
		}
		Init();
		int ans = inf;			
		sort(p+1,p+1+m,cmp);
		for(int i=0;i<(1<<q);i++){   //开始枚举套餐
			int sum = 0;
			for(int j=1;j<=n;j++)	fa[j] = j;
			for(int j=0;j<q;j++)
				if(i&(1<<j)){
					updata(j);
					sum += cost[j];  //
				}  //选中第j个套餐
			sum += Kruskal();
			ans = min(ans,sum);
		}
		printf("%d\n",ans);
		flag = true;
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值