[USACO18OPEN]Talent Show(分数规划+01背包)

传送门

题意:N头牛,第i头牛重量wi,价值vi,选出总重量至少为W的牛,使得总价值与总重量的比值最大?

看到这种类似于一个物品有多个属性,且题目最后要求各属性总和比值最大的问题,就要想到分数规划.理解题意,我们要求的答案就是\(\frac{\sum vi}{\sum wi}\),且\(\sum wi>=W\)

我们直接二分比值mid(为什么可以二分,我在其它关于分数规划的题解里讲得比较清楚了),如果本次二分的值成立,即\(\frac{\sum vi}{\sum wi}>=mid\),且\(\sum wi>=W\)

整理一下上式得\(\sum (vi-wi*mid)>=0\),且\(\sum wi>=W\)

所以我们可以在每次check时,把每头奶牛的价值视作\(vi-wi*mid\),然后题目就转换为了,已知物品的价值和重量,选出总重量不小于W的物品,使价值和>=0.这不就类似于01背包问题?

只是在跑01背包的时候注意一个问题,一般的01背包问题都是背包容量不超过W,而本题物品总重量却是要求不小于W,所以我们可以把超过W的全都压在W里面,具体见代码实现.

int n,W,w[300],t[300];
double eps=1e-8,v[300],f[1005];
//因为是实数二分,随便设置了个精度eps
bool check(double mid){
    for(int i=1;i<=W;i++)f[i]=-1e9;
//因为要求最大值,初值就设为负无穷.
    for(int i=1;i<=n;i++)
		v[i]=t[i]-w[i]*mid;
//每次check时根据mid更新每头奶牛的价值
    for(int i=1;i<=n;i++)
	for(int j=W;j>=0;j--){
	    if(w[i]+j>W)f[W]=max(f[W],f[j]+v[i]);
	    else f[w[i]+j]=max(f[w[i]+j],f[j]+v[i]);
	}
//一般的01背包都是f[j]=max(f[j],f[j-w[i]+v[i])
//但因为我们选的物品总重量只有下限,没有上限,
//所以我们这里写作:如果没选这件物品,容量j就加上w[i]
//如果选了这件物品,直接总价值f[j]加上v[i]
//然后如果容量j+w[i]超过W,还是压在f[W]里面
    return f[W]>=0;
//价值和>=0说明本次二分的mid合法
}
int main(){
    n=read();W=read();
    for(int i=1;i<=n;i++){
		w[i]=read();
		t[i]=read();
    }
    double l=0,r=1e9,mid;
    while(l+eps<r){
		mid=(l+r)/2.0;
		if(check(mid))l=mid;
		else r=mid;
    }
    printf("%d\n",(int)(l*1000));
//按照题目要求输出
    return 0;
}

posted on 2019-02-13 08:47  PPXppx  阅读(144)  评论(0编辑  收藏  举报