刷题记录:牛客NC14662小咪买东西&&NC15446wyh的物品

传送门:小咪买东西

传送门:wyh的物品

这两道题的题目大致相似,解法也大抵相似(双倍经验!!),接下来主要讲解wyh的物品

题目描述:

wyh学长现在手里有 n 个物品,这 n 个物品的重量和价值都告诉你,然后现在让你从中选取 k 个,问你在
所有可能选取的方案中,最大的单位价值为多少(单位价值为选取的 k 个物品的总价值和总重量的比值

首先看到这道题后,我当时是并不知道01规划这个思想的,所以我的第一想法肯定和大部分人一样直接想到了贪心思想(就是比值排一个序然后取前K个),即使当我们得知01规划是正解后我当时还未能看出贪心的错误,后来我想到了一个反例

假设我们有以下这三个分数
100/2     1000000/99999      1/2
如果贪心来说我们选取得肯定是前两个,但是显然因为假分数的问题(如果确保是真分数,我们的贪心就是正确的)
我们此时的分数2并没有分数3来的更加优秀,所以我们的贪心思想就变得假了

既然我们发现我们的贪心思想假了之后,接下来就是我们的正解了01规划

我们观察我们的问题,发现最终的答案显然是
∑ i = 1 k v i \sum_{i = 1}^k{v_i} i=1kvi \ \backslash \ ∑ i = 1 k c i \sum_{i = 1}^k{c_i} i=1kci这个的最大值(选取K个)

我们假设这个式子的值为L

我们得到

∑ i = 1 k v i \sum_{i = 1}^k{v_i} i=1kvi \ \backslash \ ∑ i = 1 k c i \sum_{i = 1}^k{c_i} i=1kci ≥ \geq L

得到 ∑ i = 1 k v i \sum_{i = 1}^k{v_i} i=1kvi ∑ i = 1 k c i \sum_{i = 1}^k{c_i} i=1kci × \times ×L ≥ \geq 0

并且当我们得到这样的一个式子之后,和之前的比值就完全不同了,因为是加减法所以此时我们的这个式子是可以拆的,也就是k个```Vi-Ci*L``个式子相加,此时我们在根据这个来排序即可

对于怎么找到我们的L,我们可以选择采用二分来找到,对于每一次我们的L,假设我们的求和式子最终的答案是大于0的话,说明我们的L是可以继续增加因为显然这个式子是关于L单调减的

这就是最朴素的01规划思想了

加下来是我们的代码部分

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <string.h>
#include <stack>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define root 1,n,1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
#define ll_maxn 0x3f3f3f3f3f3f3f3f
const double eps=1e-8;
int T;
struct Thing{
	double c,v,bizhi;
}thing[maxn];
int n,k;
bool cmp(Thing a,Thing b) {
	return a.bizhi>b.bizhi;
}
int check(double mid) {
	for(int i=1;i<=n;i++){
		thing[i].bizhi=thing[i].v-thing[i].c*mid;
	}
	sort(thing+1,thing+n+1,cmp);
	double ans=0;
	for(int i=1;i<=k;i++) {
		ans+=thing[i].bizhi;
	}
	if(ans>=0) return true;
	else return false;
}
int main() {
	T=read();
	while(T--) {
		memset(thing,0,sizeof(thing));
		n=read();k=read();
		for(int i=1;i<=n;i++) {
			thing[i].c=read();
			thing[i].v=read();
		}
		double l=0,r=1e7;double ans=0;
		while(r-l>=eps) {
			double mid=(l+r)/2;
			if(check(mid)) {
				ans=mid;
				l=mid;
			}else {
				r=mid;
			}
		}
		printf("%.2lf\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值