紫书 - UVA - 1151 Buy or Build 最小生成树+状态枚举

https://vjudge.net/problem/UVA-1151

题意:平面上有n个点,两点间的费用为两点的欧几里得距离。另外还有m个套餐,购买第i个套餐需要花费c[i],购买后套餐中的点均连通。

题解:先对原图求一次最小生成树,将原图的其他边都去掉,仅剩下这n-1条边。然后状态枚举[0,1<<m)进行购买套餐的操作,然后再求最小生成树即可。这种思路是正确的:用Kruskal算法求最小生成树时,就是每次优先选择最小的边,而购买套餐后,原图最小生成树这n-1条边仍然处于最优先考虑的位置。注意每个输出间还有空格。

代码:

#include<set>
#include<map>
#include<stack>
#include<queue>
#include<vector>
#include<string>
#include<bitset>

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<ctime>

#include<iomanip>
#include<iostream>

#define debug cout<<"aaa"<<endl
#define d(a) cout<<a<<endl
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define LL long long
#define lson l,mid,root<<1
#define rson mid+1,r,root<<1|1
#define MIN_INT (-2147483647-1)
#define MAX_INT 2147483647
#define MAX_LL 9223372036854775807i64
#define MIN_LL (-9223372036854775807i64-1)
using namespace std;

const int N = 1000 + 5;
const int M = N * N + 5;
const int mod = 1000000000 + 7;
const double eps = 1e-8;
int x[N],y[N];
int F[N];

int Find(int x){
	return x==F[x]?x:F[x]=Find(F[x]);
}

void unite(int x,int y){
	x=Find(x),y=Find(y);
	F[x]=y;
}

struct node{
	int u,v;
	int w;
	node(int u=0,int v=0,int w=0):u(u),v(v),w(w){}
}a[M],K[N];

struct Node{
	vector<int> v;
	int w;
}q[10];

int dis(int i,int j){
	return (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
}

bool cmp(node a,node b){
	return a.w<b.w;
}

int main(){
	int t,num,n,m,u,v,cnt,temp;
	int x1,x2,y1,y2,w;
	LL ans,minn;
	scanf("%d",&t);
	while(t--){
		cnt=0,ans=MAX_INT;
		scanf("%d%d",&n,&m);
		for(int i=0;i<m;i++){
			q[i].v.clear();
			scanf("%d%d",&num,&q[i].w);
			while(num--){
				scanf("%d",&u);
				q[i].v.pb(u);
			}
		}
		for(int i=1;i<=n;i++){
			scanf("%d%d",&x[i],&y[i]);
			F[i]=i;
			for(int j=1;j<i;j++){
				a[++cnt]=node(i,j,dis(i,j));
			}
		}
		sort(a+1,a+cnt+1,cmp);num=0;
		for(int i=1;i<=cnt;i++){
			u=a[i].u,v=a[i].v;
			if(Find(u)!=Find(v)){
				unite(u,v);
				K[++num]=a[i];
				if(num==n-1){
					break;
				}
			}
		}
		for(int i=0;i<(1<<m);i++){
			minn=0;
			for(int j=1;j<=n;j++){
				F[j]=j;
			}
			for(int j=0;j<m;j++){
				if(i&(1<<j)){
					minn+=q[j].w;
					for(int k=1;k<q[j].v.size();k++){
						unite(q[j].v[0],q[j].v[k]);
					}
				}
			}
			for(int j=1;j<=n-1;j++){
				u=K[j].u,v=K[j].v;
				if(Find(u)!=Find(v)){
					unite(u,v);
					minn+=K[j].w;
				}
			}
			ans=min(ans,minn);
		}
		printf("%lld\n",ans);
		puts("");
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值