UVA1151 买还是建 Buy or Build

买还是建 Buy or Build - 洛谷

第一道A掉的紫题。。(虽然比较水)

修修改改写了将近五六个小时,还是在紫书的帮助下写的。

不过正解想出来后也就很快A了,(除了忘了重置sum的值WA了一发)

题解:
 

这题其实相当于一个暴力,枚举每个套餐是否会被选到,q很小,最大为8。2^8也仅有256种情况

枚举完套餐,把这些套餐中包含的点加一个权值为0的边然后求最小生成树就行。

粗略分析一下时间复杂度:

先建立每个点之间的边,则有n*(n+1)/2条边,也就是n²量级条边

排序是n²logn²≈n²logn

然后枚举套餐过程是(2^q)

Kruskal算法的复杂度基本上就是排序的复杂度(O(mlogm)时间主要花在排序上)也就是n²logn

总的时间复杂度为(2^q)·n²logn(与紫书貌似有些出入)  基本上达到了10^8级别,这是不能接受的

我们需要先做一遍最小生成树,把无用的边去掉

剩下的话只有n-1条边了,枚举量大大减小.

优化后时间复杂度为(2^q)·nlogn+nlogn 大概只有10^5~10^6左右

正如lrj在原文中说的:因为Kruskal在连通分量包含n个点时会终止,所以对于随机数据,即使用原始的“暴力算法”,也能很快出解

思路是:

①先建立所有点之间的边,然后求其最小生成树,把无用的边去掉(这步很重要)

②然后我们就可以开始枚举套餐了。

我想的是用递归来枚举,当时也踩了不少坑(如:当时想每一层递归进行加边/恢复边处理,实则使问题复杂化了)

最好是先开一个数组记录一下每个套餐选不选

③递归枚举完后调用Kruskal函数,挨个判断套餐选不选,选的话直接加一个权值为0的边(也不需要开一个二维数组,使问题更复杂了...),然后求最小生成树,并记录一下花费就好了

各个步骤的代码如下:

①:

		//先求一次原图的最小生成树得到n-1条边
        int k=0;
		for(int i=1;i<=n;i++)
		{
			for(int j=i+1;j<=n;j++)
			{
				int d=(y[j]-y[i])*(y[j]-y[i])+(x[j]-x[i])*(x[j]-x[i]);
				t[k++]={i,j,d};
				
			}
		}
		
		int idx=0;
		sort(t,t+k,com);

		for(int i=1;i<=n;i++) p[i]=i;
		int cnt=0;
		for(int i=0;i<k;i++)
		{
			int a=t[i].a,b=t[i].b;
			a=find(a),b=find(b);
			if(a!=b)
			{
				p[a]=b;
				e[idx++]=t[i];//把无用的边扔掉,有用的存进e里
				if(++cnt==n-1) break;
			}
		}

int solve(int step)
{
	if(step==q+1)
	{

		int t=kruskal();
	//	printf(":%d\n",t);
		
		ans=min(ans,t);
	}
	else
	{
		vis[step]=0;
		
		solve(step+1);
		
		vis[step]=1;
		
		solve(step+1);
		
	}
	return 0;
}

③(这一步注释就先不删了)

int kruskal()
{
//	printf("\n选择方式:");
//	for(int i=1;i<=q;i++)
//	{
//		printf("%d ",vis[i]);
//	}	
//	printf("\n");
	for(int i=1;i<=n;i++)p[i]=i;//初始化并查集
	//复制边
	for(int i=0;i<n-1;i++) kru[i]=e[i];//0~n-2共n-1条边
	int idx=n-1;
	int sum=0;
//	printf("\n加加边前:\n");
//	for(int i=0;i<idx;i++)
//	{
//		printf("%d %d %d\n",kru[i].a,kru[i].b,kru[i].c);
//		
//	}
//	printf("加的边有:\n");
	for(int i=1;i<=q;i++)
	{
		if(vis[i])
		{
			sum+=meal[i].price;
		
			for(int j=2;j<=meal[i].m;j++)
			{
				kru[idx++]={meal[i].city[1],meal[i].city[j],0};
		//		printf("%d %d %d\n",kru[idx-1].a,kru[idx-1].b,kru[idx-1].c);
			}
		}
	}
	sort(kru,kru+idx,com);
//	printf("\n加完边后:\n");
//	for(int i=0;i<idx;i++)
//	{
//		printf("%d %d %d\n",kru[i].a,kru[i].b,kru[i].c*kru[idx-1].c);
//		
//	}
	for(int i=0;i<idx;i++)
	{
		int a=kru[i].a,b=kru[i].b;
		a=find(a),b=find(b);
		if(a!=b)
		{
		//	printf("%d %d :%d\n",kru[i].a,kru[i].b,kru[i].c);
			p[a]=b;
			sum+=kru[i].c;
		}
	}
	return sum;
	
}

至此,主要代码部分完成

完整代码献上

#include<stdio.h>
#include<iostream>
#include<cstdlib>
#include<string.h>
#include<algorithm>
using namespace std;
const int N=1010,M=1e6+10;
int n,q;
struct meal
{
	int m;
	int price;
	int city[1000];
}meal[10];
int vis[10];
struct edge
{
	int a,b,c;
}e[M],t[M];
bool com(edge a,edge b)
{
	return a.c<b.c;
}
int p[N];
int find(int x)
{
	if(p[x]!=x)
	{
		p[x]=find(p[x]);
	}
	return p[x];
}
int x[N],y[N];
edge kru[M];
int kruskal()
{
//	printf("\n选择方式:");
//	for(int i=1;i<=q;i++)
//	{
//		printf("%d ",vis[i]);
//	}	
//	printf("\n");
	for(int i=1;i<=n;i++)p[i]=i;//初始化并查集
	//复制边
	for(int i=0;i<n-1;i++) kru[i]=e[i];//0~n-2共n-1条边
	int idx=n-1;
	int sum=0;
//	printf("\n加加边前:\n");
//	for(int i=0;i<idx;i++)
//	{
//		printf("%d %d %d\n",kru[i].a,kru[i].b,kru[i].c);
//		
//	}
//	printf("加的边有:\n");
	for(int i=1;i<=q;i++)
	{
		if(vis[i])
		{
			sum+=meal[i].price;
		
			for(int j=2;j<=meal[i].m;j++)
			{
				kru[idx++]={meal[i].city[1],meal[i].city[j],0};
		//		printf("%d %d %d\n",kru[idx-1].a,kru[idx-1].b,kru[idx-1].c);
			}
		}
	}
	sort(kru,kru+idx,com);
//	printf("\n加完边后:\n");
//	for(int i=0;i<idx;i++)
//	{
//		printf("%d %d %d\n",kru[i].a,kru[i].b,kru[i].c*kru[idx-1].c);
//		
//	}
	for(int i=0;i<idx;i++)
	{
		int a=kru[i].a,b=kru[i].b;
		a=find(a),b=find(b);
		if(a!=b)
		{
		//	printf("%d %d :%d\n",kru[i].a,kru[i].b,kru[i].c);
			p[a]=b;
			sum+=kru[i].c;
		}
	}
	return sum;
	
}
int ans=0x3f3f3f3f;

int solve(int step)
{
	if(step==q+1)
	{

		int t=kruskal();
	//	printf(":%d\n",t);
		
		
		ans=min(ans,t);
	}
	else
	{
		vis[step]=0;
		
		solve(step+1);
		
		vis[step]=1;
		
		solve(step+1);
		
	}
	return 0;
}
int main()
{
//	freopen("uva1151.txt","r",stdin);
	int T;
	scanf("%d",&T);
	int num=0;
	while(T--)
	{
		scanf("%d%d",&n,&q);
		for(int i=1;i<=q;i++)
		{
			int m;
			scanf("%d",&m);
			int price;
			scanf("%d",&price);
			meal[i].m=m,meal[i].price=price;
			for(int j=1;j<=m;j++)
			{
				scanf("%d",&meal[i].city[j]);
			}
		}
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d",&x[i],&y[i]);
		}
		//建立边数 0~k-1   k=n*(n+1)/2
		int k=0;
		for(int i=1;i<=n;i++)
		{
			for(int j=i+1;j<=n;j++)
			{
				int d=(y[j]-y[i])*(y[j]-y[i])+(x[j]-x[i])*(x[j]-x[i]);
				t[k++]={i,j,d};
				
				//g[i][j]=abs(y[j]-y[i])+abs(x[j]-x[i]);
			}
		}
		//先求一次原图的最小生成树得到n-1条边
		int idx=0;
		sort(t,t+k,com);
//		for(int i=0;i<k;i++)
//		{
//			printf("%d %d %d\n",t[i].a,t[i].b,t[i].c);
//		}
		for(int i=1;i<=n;i++) p[i]=i;
		int cnt=0;
		for(int i=0;i<k;i++)
		{
			int a=t[i].a,b=t[i].b;
			a=find(a),b=find(b);
			if(a!=b)
			{
				p[a]=b;
				e[idx++]=t[i];
				//if(t[i].a==6&&t[i].b==7)printf("----%d---",t[i].c);
				//if(a==6&&b==7)printf("-----%d---",t[i].c);
				
				//cout<<t[i].c<<endl;
				if(++cnt==n-1) break;
			}
		}
		//2^p种情况
		
		
		solve(1);
		if(++num!=1) cout<<endl;
		cout<<ans<<endl;
		ans=0x3f3f3f3f;
	}
	
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值