題意:有n個物品的重量和價值分別是wi和vi。從中選出k個物品使得單位重量的價值最大。
題解:首先考慮二分做法
那麼一般最先想到的方法是把物品按照單位價值進行排序,從小到大貪心地進行選取。但是這個方法對於很多數據都有bug,所以是不行的。
實際上,對於這個問題使用二分搜索可以很好地解決,定義:
條件C(x):=可以選擇使得單位重量的價值不小於x
因此,原來的問題就變成了求滿足C(x)的最大的x。那麼怎麼判斷C(x)是否可行呢?假設我們選了某個物品的集合S,那麼它們的單位重量的價值是
因此就變成了判斷是否存在S滿足下面的條件
把這個不等式進行變形得到;
因此,可以對(vi-x*wi)的值進行排序貪心地進行選取。因此就變成了
C(x)=((vi-x*wi)從大到小排列中地前K個的和不小於0)
每次判斷的複雜度是O(nlogn).
附上代碼:(注意精度問題,以及二分玄學操作)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1e5+50;
const int INF=1e7;
int n,k;
int v[maxn],w[maxn];
struct node{
double val;
int id;
};
node nodes[maxn];
bool cmp(node a,node b)
{
return a.val>b.val;
}
int ok(double x)
{
for(int i=0;i<n;i++){
nodes[i].val=v[i]-x*w[i];
nodes[i].id=i+1;
}
sort(nodes,nodes+n,cmp);
double sum=0;
for(int i=0;i<k;i++){
sum+=nodes[i].val;
}
return sum>=0;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++){
scanf("%d%d",&v[i],&w[i]);
}
double lb=-1,ub=INF;
while(ub-lb>1e-8){
double mid=(lb+ub)/2;
if(ok(mid)){
lb=mid;
}else{
ub=mid;
}
}
for(int i=0;i<k;i++){
printf("%d",nodes[i].id);
if(i<k-1){
printf(" ");
}else{
printf("\n");
}
}
return 0;
}