BZOJ5281:[Usaco2018 Open]Talent Show

我对二分的理解:https://www.cnblogs.com/AKMer/p/9737477.html

题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=5281

题目要求我们最大化(frac{sum{t_i}}{sum{w_i}}),我们可以二分它的值(x)。如果存在某一种方案使得(frac{sum{t_i}}{sum{w_i}}>=x)

,我们可以将其转化成有一种方案满足(sum{t_i}-sum{w_i*x}>=0)。于是乎我们就可以将(t_i-w_i*x)作为一只奶牛的权值,体重为空间来做背包,判断是否可以用(W)的体积背出大于等于(0)的权值。而且显然,(t_i-w_i*x)大于等于(0)的奶牛肯定会被选中,那么如果需要用到(t_i-w_i*x)为负数的奶牛,肯定是尽量少选。所以我们在背包时,如果从某个状态转以后体积大于(W)了,就可以停下转移了,因为再加也没用了。

时间复杂度:(O(log1e9*n*W))

空间复杂度:(O(n))

代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;

const double eps=1e-6,inf=1e9;

int n,W;
int w[255],t[255];
double now[255],f[1005];

int read() {
	int x=0,f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
	return x*f;
}

bool check(double limit) {
	for(int i=1;i<=n;i++)
		now[i]=1.0*t[i]-limit*w[i];//更改权值
	for(int i=1;i<=W;i++)f[i]=-inf;
	for(int i=1;i<=n;i++) {
		for(int j=W;j>=W-w[i]&&~j;j--)
			f[W]=max(f[W],f[j]+now[i]);//转移后大于W的只转移一次
		for(int j=W-1;j>=w[i];j--)
			f[j]=max(f[j],f[j-w[i]]+now[i]);//背包
	}
	return f[W]>eps;
}

int main() {
	n=read(),W=read();
	for(int i=1;i<=n;i++)
		w[i]=read(),t[i]=read();
	double l=0,r=1e3;
	while(l+eps<r) {
		double mid=(l+r)/2;
		if(check(mid))l=mid;//二分x,[0,l]都是可以凑出来的值
		else r=mid;
	}
	printf("%d
",(int)(l*1000));//l是最大的那一个
	return 0;
}
原文地址:https://www.cnblogs.com/AKMer/p/9746894.html