洛谷 P1194 买礼物 (图论 最小生成树)

48 篇文章 0 订阅
13 篇文章 2 订阅

鸽了好几天了今天写个洛谷的题解

题目描述

又到了一年一度的明明生日了,明明想要买 BB 样东西,巧的是,这 BB 样东西价格都是 AA 元。

但是,商店老板说最近有促销活动,也就是:

如果你买了第 II 样东西,再买第 JJ 样,那么就可以只花 K_{I,J}KI,J​ 元,更巧的是,K_{I,J}KI,J​ 竟然等于 K_{J,I}。

现在明明想知道,他最少要花多少钱。

输入格式

第一行两个整数,A,B。

接下来 B 行,每行 B个数,第 I 行第 J 个为 K_{I,J}。

我们保证 K_{I,J}=K_{J,I},并且 K_{I,I}=0。

特别的,如果 K_{I,J}=0,那么表示这两样东西之间不会导致优惠。

 

输出格式

一个整数,为最小要花的钱数。

 

 

输入输出样例

输入 #1

1 1
0

输出 #1

1

输入 #2

3 3
0 2 4
2 0 2
4 2 0

输出 #2

7

题目的确很短,但如果你抽象不出来,啥用没有

图论的题只要抽象出来是什么算法直接套模板了

以这题为例:他说买两样物品会有优惠,问买b件物品最少需要多少钱

像这种有n个东西,n个东西之间还有关联的一看就是图论

确定了这题属于图论之后

那么现在来抽象点和边:点很简单,就是b件物品

而边往往是最难的,在这题里,如果两个物品之间有优惠,就建一条边,权值为优惠价格

 还有一种可能,就是没有优惠,很简单,这就不建边

按照上面的方法抽象后,样例2如下:

看起来没什么问题对吧?

(如果此时你心中有疑问,保留起来,接着往下看)

 

他要买b件物品,在图中就是遍历一遍图,而且要最小权值

根据这几个关键词,你应该可以想到一个➡➡➡最小生成树

直接套入模板上交:

# include <iostream>
# include <cstdio>
# include <algorithm>
using namespace std;
# define int long long
int a,b,ans=0;
struct node{
	int u,v,w;
}e[505*505];
int m;
int f[505],h[505]; 
void add(int u,int v,int w){
	e[++m].u=u;
	e[m].v=v;
	e[m].w=w;
}
void make(){
	for (int i=1;i<=b;i++){
		f[i]=i;
		h[i]=1;
	}
}
bool cmp(node x,node y){
	return x.w<y.w;
}
int find(int x){
	if (f[x]!=x){
		return f[x]=find(f[x]);
	}
	return f[x];
}
void join(int a,int b){
	if (a==b){
		return ;
	}
	if (h[a]>=h[b]){
		f[b]=a;
		h[a]+=h[b];
		h[b]=0;
	}else{
		f[a]=b;
		h[b]+=h[a];
		h[a]=0;
	}
}
void kruskal(){
	make();
	sort(e+1,e+1+m,cmp);
	for (int i=1;i<=m;i++){
		int a=find(e[i].u),b=find(e[i].v);
		if (a==b){
			continue;
		}
		ans+=e[i].w;
		join(a,b);
		//printf("%lld %lld\n",a,b);
	}
	return ;
}
signed main(){
	scanf("%lld%lld",&a,&b);
	for (int i=1;i<=b;i++){
		for (int j=1;j<=b;j++){
			int num;
			scanf("%lld",&num);
			if (num!=0){
				add(i,j,num);
			}
		}
	}
	kruskal();
	printf("%lld",ans);
	return 0;
}

这时候你心中的疑问就起作用了

我们深入到kruskal函数里:

 

 

这里有一个排序语句 

再回到我们刚才的图中

这条边最短,优先考虑 

而这条边被计入了两次,相当于重复建边,所以我们要加一个判断

就变成了这样:

# include <iostream>
# include <cstdio>
# include <algorithm>
using namespace std;
# define int long long
int a,b,ans=0;
struct node{
	int u,v,w;
}e[505*505];
int m;
int f[505],h[505]; 
void add(int u,int v,int w){
	e[++m].u=u;
	e[m].v=v;
	e[m].w=w;
}
void make(){
	for (int i=1;i<=b;i++){
		f[i]=i;
		h[i]=1;
	}
}
bool cmp(node x,node y){
	return x.w<y.w;
}
int find(int x){
	if (f[x]!=x){
		return f[x]=find(f[x]);
	}
	return f[x];
}
void join(int a,int b){
	if (a==b){
		return ;
	}
	if (h[a]>=h[b]){
		f[b]=a;
		h[a]+=h[b];
		h[b]=0;
	}else{
		f[a]=b;
		h[b]+=h[a];
		h[a]=0;
	}
}
void kruskal(){
	make();
	sort(e+1,e+1+m,cmp);
	for (int i=1;i<=m;i++){
		int a=find(e[i].u),b=find(e[i].v);
		if (a==b){
			continue;
		}
		ans+=e[i].w;
		join(a,b);
		//printf("%lld %lld\n",a,b);
	}
	return ;
}
signed main(){
	scanf("%lld%lld",&a,&b);
	for (int i=1;i<=b;i++){
		for (int j=1;j<=b;j++){
			int num;
			scanf("%lld",&num);
			if (num!=0&&i<j){
				add(i,j,num);
			}
		}
	}
	kruskal();
	printf("%lld",ans);
	return 0;
}

还是回归题目看看

 

注意下这句话

在我们这张图里对待=0这种情况就是跳过

但如果这个点没有和其他任何一个点有优惠的话这个点就遍历不到了

 所以还需要为这些点特意建一条边,并且不能影响其他的点

可以考虑与0建边

如果一个点没有优惠或者优惠还不如原价的话,就会直接使用原价

新建的边也会存入e结构体

# include <iostream>
# include <cstdio>
# include <algorithm>
using namespace std;
# define int long long
int a,b,ans=0;
struct node{
	int u,v,w;
}e[505*505];
int m;
int f[505],h[505]; 
void add(int u,int v,int w){
	e[++m].u=u;
	e[m].v=v;
	e[m].w=w;
}
void make(){
	for (int i=1;i<=b;i++){
		f[i]=i;
		h[i]=1;
	}
}
bool cmp(node x,node y){
	return x.w<y.w;
}
int find(int x){
	if (f[x]!=x){
		return f[x]=find(f[x]);
	}
	return f[x];
}
void join(int a,int b){
	if (a==b){
		return ;
	}
	if (h[a]>=h[b]){
		f[b]=a;
		h[a]+=h[b];
		h[b]=0;
	}else{
		f[a]=b;
		h[b]+=h[a];
		h[a]=0;
	}
}
void kruskal(){
	make();
	sort(e+1,e+1+m,cmp);
	for (int i=1;i<=m;i++){
		int a=find(e[i].u),b=find(e[i].v);
		if (a==b){
			continue;
		}
		ans+=e[i].w;
		join(a,b);
		//printf("%lld %lld\n",a,b);
	}
	return ;
}
signed main(){
	scanf("%lld%lld",&a,&b);
	for (int i=1;i<=b;i++){
		for (int j=1;j<=b;j++){
			int num;
			scanf("%lld",&num);
			if (num!=0&&i<j){
				add(i,j,num);
			}
		}
	}
	for (int i=1;i<=b;i++){
		add(0,i,a);
	}
	kruskal();
	printf("%lld",ans);
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值