初识贪心算法

P2240 【深基12.例1】部分背包问题

题目描述
阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 N(N≤100) 堆金币,第 i 堆金币的总重量和总价值分别是 mi,vi(1 <= mi,vi <= 100) 。阿里巴巴有一个承重量为 T(T≤1000) 的背包,但并不一定有办法将全部的金币都装进去。他想装走尽可能多价值的金币。所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变。请问阿里巴巴最多可以拿走多少价值的金币?

输入格式
第一行两个整数 N,T。

接下来 N 行,每行两个整数 mi,vi。

输出格式
一个实数表示答案,输出两位小数

输入

4 50
10 60
20 100
30 120
15 45

输出

240.00

我的思路:

因为最近在学贪心算法,所以我知道是贪心,但根据题意知,求最大价值,也可以知道一二吧。

因为 所有金币都可以随意分割,分割完的金币重量价值比不变 ,所以我们是尽可能拿到许多重量价值比高的金币,所以我们使用sort函数来进行快排,方便拿去高的金币。

故产生了一个贪心算法:在可选的金币堆中,每次都选取价值比高的金币,其次是价值比较高的金币,以此类推。

这里我运用了pair类型来存储数据,当然也可以用结构体;并使用了pair的sort快排。(具体实现方法见我另一篇文章

提醒:要学会现学现用。

注意:输出的为实数,所以这里使用小数除法,而不是整数除法,不要被样例的表面所迷惑。

代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>//sort函数的头文件
using namespace std;

typedef pair<int ,int > P;
int n,t;

//sort方法自定义排序规则:按降序排序。
bool cmp(P a,P b){
	return a.second * 1.0/a.first > b.second * 1.0/b.first;
}

int main()
{
	cin >> n >> t;
	P *p = new P[n + 1];
	for (int i = 1;i <= n;i ++){
		cin >> p[i].first >> p[i].second;
	}
	sort(p + 1,p + n + 1,cmp);
	double ans = 0;
	for (int i = 1;i <= n;i ++){
		if (p[i].first <= t){ //没满就将一堆全装。
			ans += p[i].second;
			t -= p[i].first;
		} else {
			ans += t * p[i].second * 1.0 / p[i].first;
			break;
		}
	}
	printf("%.2lf",ans);
	return 0;
}

大佬的思路:

按性价比从大到小排序,再挨个装包,直到装不下为止。

开一个结构体gold来存储 m,vm,v,使用自定义比较函数和sort来进行排序。

代码如下:

#include <bits/stdc++.h>
using namespace std;

struct gold{
	int m,v;//m is the weight,v is the money.
};
//自定义快排规则
bool cmp(gold a,gold b){
	return a.v*b.m>b.v*a.m;
}
//快读,并标记了inline内联函数。
inline int read(){
	char c;
	bool flag=false;
	while((c=getchar())<'0'||c>'9')
	    if(c=='-') flag=true;
	int res=c-'0';
	while((c=getchar())>='0'&&c<='9')
	    res=(res<<3)+(res<<1)+c-'0';
	return flag? -res:res;
}

gold a[110];

int main(){
	int n=read(),t=read();
	double coin=0;
	for(int i=0;i<n;i++){
		a[i].m=read();
		a[i].v=read();
	}
	sort(a,a+n,cmp);
	int i;
	for(i=0;i<n;i++){
		if(t<a[i].m) break;//这里操作放在了循环外面进行了。
		coin+=a[i].v;
		t-=a[i].m;
	}
	if(i<n) coin+=1.0*t*a[i].v/a[i].m;
	printf("%.2lf",coin);
	return 0;
}

总结:

1.快读(read)

快读:速度比cin和scanf快,据说快了1ms。

读入一个int的案例:

inline int read(){
	char c;
	bool flag=false;	//判断是否有负号。
	while((c=getchar())<'0'||c>'9')	//排除杂项(即不是数字的值,如:空白字符)
	    if(c=='-') 
	    	flag=true;
	int res=c-'0';	//因为读入数字后退出循环,这时c是数字,而不是杂项。
	while((c=getchar()) >= '0' && c <= '9')
	    res=(res<<3)+(res<<1)+c-'0';	//通过位运算来提高效率来实现进位,而不是乘以10。
	return flag? -res:res;//通过三目运算符
}

(res<<3) + (res<<1) == (res * 8 + res * 2)
左移:res << n;乘以2的n次方。
右移:res >> n;除以2的n次方。

了解更多快读和快输(👈传送门)

2.三目运算符 和 if-else 的比较

可读性:三目运算符比 if-else 强。
因为三目运算符比较简洁而且易读性较强。

效率差异
在没有编译器优化的情况下三目运算符比If-else慢,因为三目运算符还会使用额外的临时变量,它先运算后赋值,If-else是直接赋值,不存在运算,所以速度会很快;

temp:临时变量,或者说是传参赋值的变量。
temp = flag ? -res:res; 
temp = read();	//int read() {return flag ? -res:res; }

但是现在的编译器已经会把这些做优化,优化后的汇编代码是一样的,就好比i++和++i,编译优化后的汇编代码是一样的。
i ++:先赋值,再运算。
++ i:先运算,再赋值。

完整的证明过程(👈传送门)

3.避免使用除法的骚操作return a.v*b.m > b.v*a.m;

本来应该是 a.v / a.m > b.v / b.m;

证明:
∵ x ÷ y = z 代表 x = z × y,
∴ a.v ÷ a.m > b.v ÷ b.m
 a.v > b.v ÷ b.m × a.m
 a.v × b.m > b.v × a.m
这样,可以避免出现浮点数,增加精确度。

4.代码解读if(i<n) coin+=1.0 * t * a[i].v / a[i].m;

*1.0是进行小数除法;* t是这堆金币无法装完了,所以乘t;
若 i<n ,则将背包的剩余容量(t)全装满第 i 种金币,加上金币的价钱((t×单价(a[i].v/a[i].m))。


补充方法

不是用结构体,不使用快排(sort)。

#include<bits/stdc++.h>
using namespace std;
int n;//有n堆金币
double t,v[101],w[101],a[101],ans;//t:背包容量 v:价值 w:重量 a:性价比 ans:答案
int main()
{
	cin>>n>>t;//读入
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>v[i];
		a[i]=v[i]/w[i];//求性价比
	}
	for(int i=1;i<=n;i++)//冒泡排序
	{
		for(int j=1;j<n;j++) 
		{
			if(a[j]<a[j+1])
			{
				swap(a[j],a[j+1]);
				swap(v[j],v[j+1]);
				swap(w[j],w[j+1]);
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(t-w[i]>-0.000001)//浮点数不能相等
		{
			t-=w[i];
			ans+=v[i];
		}
		else
		{
			ans+=t*a[i];//把金币分割
			break;//跳出循环
		}
 	}
 	printf("%.2lf",ans);//保留两位小数输出
 	return 0;//完美结束
}

总结

1.代码解析a[i]=v[i]/w[i];//求性价比

误区纠正:在C语言和C++中,进行除法运算时,操作数至少有一个为浮点数,就进行小数除法。如果两边都是整数时,进行整数除法,要想进行小数除法:c = a * 1.0 / b;

2.代码解析if(t-w[i]>-0.000001)//浮点数不能相等

因为小数除法在没有格式指示符的情况下,输出6位小数
这样写等价于:t - w[i] > 0

3.swap()(交换函数)

1.头文件: #include<algorithm>
2.好处:①可以避免交换时,精度缺失;
    ②结构体的交换:结构体需要对每个数据都进行交换,这时使用swap函数方便交换。

关于swap函数的好处证明

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值