首先引出0/1分数规划的概念
给出一列数$a_i$与$b_i$,构造出一列数$x_i$,使得$\frac{\sum^{n}_{i=1}{a_i*{x_i}}}{\sum^{n}_{i=1}{b_i*{x_i}}}$最大,求出这个最大值(满足$x_i\in\lbrace{0,1}\rbrace$)
想要求出这个最大值一般使用二分答案
即当当前枚举的答案是$mid$时
判断是否有$\frac{\sum^{n}_{i=1}{a_i*{x_i}}}{\sum^{n}_{i=1}{b_i*{x_i}}}\geq mid$
如果有则$l=mid$,否则$r=mid$
那么如何快速求出不等式的左边呢?我们将原不等式变形
$$ \sum^n_{i=1}{a_i*x_i}\geq {mid*\sum^n_{i=1}{b_i*x_i}} $$
$$ \sum^n_{i=1}{a_i*x_i}-{mid*\sum^n_{i=1}{b_i*x_i}}\geq 0 $$
不等式左边将$x_i$提出去
$$x_i*(\sum_{i=1}^n{a_i-mid*b_i})\geq 0$$
因此我们可以在$O(n)$的时间内将$a_i-mid*b_i$处理出来,然后做一个简单的贪心
如果这个值$\geq 0$,那么令这个$x_i=1$,否则令$x_i=0$
关于总的时间复杂度,二分答案$O(logn)$,检验答案合法性$O(n)$,总时间复杂度为$O(nlogn)$
一道例题
题目链接http://poj.org/problem?id=2976
看到式子就知道这大概是个模板题了
唯一的不同是这里只要求选出$n-k+1$个数
那么只要将所有的$\sum_{i=1}^n{a_i-mid*b_i}$排序,直接选取前$n-k+1$个数相加即可
由于保证$a_i<b_i$,所以二分边界只要是$[0,1]$即可
#include<iostream> #include<string> #include<string.h> #include<stdio.h> #include<algorithm> #include<vector> #include<queue> #include<map> using namespace std; int a[1010],b[1010],n,k; double sum[1010]; bool check(double num) { int i; for (i=1;i<=n;i++) sum[i]=(double)a[i]-num*b[i]; sort(sum+1,sum+1+n); double s=0.0; for (i=k+1;i<=n;i++) s+=sum[i]; if (s>=0) return 1;else return 0; } int main() { scanf("%d%d",&n,&k); while ((n!=0) || (k!=0)) { int i; for (i=1;i<=n;i++) scanf("%d",&a[i]); for (i=1;i<=n;i++) scanf("%d",&b[i]); double l=0.0,r=1.0; while (r-l>1e-4) { double mid=(l+r)/2; if (check(mid)) l=mid; else r=mid; } printf("%.0lf\n",l*100); scanf("%d%d",&n,&k); } return 0; }