UVA 1151 Buy or Build(最小生成树,Kruskal)

3 篇文章 0 订阅

UVA 1151 Buy or Build(最小生成树,Kruskal)

对于n(1<=n<=1000)个点,q(0<=q<=8)个套餐,如果直接建边并枚举套餐的话,每次Kruskal算法要处理的节点数就会高达O(n2)个,那么整个算法的时间复杂度就高达O(2q n2+n2logn2)。其中O(n2logn2)是排序的时间复杂度。

为了降低时间复杂度,可以先通过Kruskal算法在不选套餐的情况下生成最小生成树,那么剩下的边只有n-1个,那么后面只需要处理这n-1条边和套餐里的边,算法的效率有了大大的提高。时间复杂度为O(2q n+nlongn)。

有个可能的问题是,后续本来要选的边会不会通过预处理被忽略掉了?

答案是不会,Kruskal算法以边的权值作为排序依据,并且只选两个端点不属于一个连通分量的边。由于把套餐内边的权值设置为0,对于每一个不在套餐内的边,排在它前面的边只增不减。如果该边在预处理的时候被抹去,那么在后续中也会被抹去。

我一开始有个想法是,将一个套餐里的点设置为同一个代表元,这样Kruskal算法就会忽略以这些点为端点的边,但是问题是,一个点可能会出现在多个套餐里面,这样只能记录这个点所在的最后一个套餐里的代表元,那么会导致在先前的套餐里本来应是权值为0的边消失。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int max_n=1e6;
int x[1005],y[1005],p[1005];
int pre_u[max_n],pre_v[max_n],pre_r[max_n],u[5000],v[5000],r[5000];
int pre_w[max_n],w[5000];
int price[10];
vector<int> point[10];
int n,q;
int mcount;
int distance(int i,int j)
{
	int a=x[j]-x[i];
	int b=y[j]-y[i];
	return (a*a+b*b);
}
bool cmp_1(const int i,const int j)
{
	return pre_w[i]<pre_w[j];
}
bool cmp_2(const int i,const int j)
{
	return w[i]<w[j];
}
int find(int x)
{
	return p[x]==x?x:p[x]=find(p[x]);
}
long long kruskal(void)
{
	long long ans=0;
	for(int i=0;i<mcount;i++)r[i]=i;
	for(int i=1;i<=n;i++)p[i]=i;
	sort(r,r+mcount,cmp_2);
	for(int i=0;i<mcount;i++)
	{
		int e=r[i];
		int x=find(u[e]);
		int y=find(v[e]);
		if(x!=y){
			p[x]=y;
			ans+=w[e];
		}
	}
	return ans;
}
void solve(void)
{
	int cnt=0;
	mcount=0;
	long long ans=0;
	for(int i=1;i<=n;i++)
	for(int j=i+1;j<=n;j++)//建边 
	{
		pre_r[cnt]=cnt;
		pre_u[cnt]=i;
		pre_v[cnt]=j;
		pre_w[cnt++]=distance(i,j);
	}
	sort(pre_r,pre_r+cnt,cmp_1);
	for(int i=1;i<=n;i++)p[i]=i;
	for(int i=0;i<cnt;i++)
	{
		int e=pre_r[i];
		int x=find(pre_u[e]);
		int y=find(pre_v[e]);
		if(x!=y){
			p[x]=y;
			ans+=pre_w[e];
			u[mcount]=pre_u[e];
			v[mcount]=pre_v[e];
			w[mcount++]=pre_w[e];
		}
		if(mcount==n-1)break;
	}//筛选出n-1条边
	for(int i=1;i<(1<<q);i++)
	{
		mcount=n-1;
		long long cost=0;
		for(int j=0;j<q;j++)
		if(i&(1<<j)){
			for(int k=0;k<point[j].size()-1;k++)
			{
				u[mcount]=point[j][k];
				v[mcount]=point[j][k+1];
				w[mcount++]=0;
			}
			cost+=price[j];
		}
		long long temp=kruskal()+cost;
		if(temp<ans)ans=temp;
	}
	cout<<ans<<endl;
}
int main(void)
{
//	freopen("out.txt","w",stdout);
	int t;
	cin>>t;
	while(t--)
	{
		scanf("%d%d",&n,&q);
		for(int i=0;i<q;i++)
		point[i].clear();
		for(int i=0;i<q;i++)
		{
			int a;
			scanf("%d%d",&a,&price[i]);
			while(a--)
			{
				int b;
				scanf("%d",&b); 
				point[i].push_back(b);
			}
		}
		for(int i=1;i<=n;i++)
		scanf("%d%d",&x[i],&y[i]);
		solve();
		if(t!=0)cout<<endl;
	}
//	fclose(stdout);
}
//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 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值