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\n",(int)(l*1000));//l是最大的那一个
	return 0;
}
posted @ 2018-10-06 11:04  AKMer  阅读(112)  评论(0编辑  收藏  举报