POJ 2976 Dropping tests 01分数规划 模板 二分&&Dinkelbach

来源:互联网 发布:拍照软件 编辑:程序博客网 时间:2024/06/10 23:31

Description

In a certain course, you take n tests. If you get ai out of bi questions correct on test i, your cumulative average is defined to be
这里写图片描述
Given your test scores and a positive integer k, determine how high you can make your cumulative average if you are allowed to drop any k of your test scores.

Suppose you take 3 tests with scores of 5/5, 0/1, and 2/6. Without dropping any tests, your cumulative average is 这里写图片描述
However, if you drop the third test, your cumulative average becomes

Input

The input test file will contain multiple test cases, each containing exactly three lines. The first line contains two integers, 1 ≤ n ≤ 1000 and 0 ≤ k < n. The second line contains n integers indicating ai for all i. The third line contains n positive integers indicating bi for all i. It is guaranteed that 0 ≤ ai ≤ bi ≤ 1, 000, 000, 000. The end-of-file is marked by a test case with n = k = 0 and should not be processed.

Output

For each test case, write a single line with the highest cumulative average possible after dropping k of the given test scores. The average should be rounded to the nearest integer.

Sample Input

3 1
5 0 2
5 1 6
4 2
1 2 7 9
5 6 7 9
0 0

Sample Output

83
100

Hint

To avoid ambiguities due to rounding errors, the judge tests have been constructed so that all answers are at least 0.001 away from a decision boundary (i.e., you can assume that the average is never 83.4997).

大意:
即从 a[i] 和 b[i] 中取出对应的 k 组数 要求 sigma(a[i])/sigma(b[i]) 最大,求这个最大值.
另一种显然的表示方法:
R=(a[i]x[i])(b[i]x[i])

所谓的01分数规划问题就是指这样的一类问题,给定两个数组,a[i]表示选取i的收益,b[i]表示选取i的代价。如果选取i,定义x[i]=1,否则x[i]=0。
每一个物品只有选或者不选两种方案,求一个选择方案即从其中选取k组a[i]/b[i],使得 R=(a[i]x[i])(b[i]x[i]) 取得最值,即所有选择物品的总收益/总代价的值最大或是最小。

01分数规划问题主要包含一般的01分数规划、最优比率生成树问题、最优比率环问题等。

对应这个目标式,我们可以整理出一个判别式
f(r)=(a[i]x[i])r(b[i]x[i])=(a[i]rb[i])x[i]
用二分答案的思想
当 f(r)>0 时,显然我们还可以取更大的 r 来使得 f(r) 逼近 0
当 f(r)<0 时,我们应取更小的 r
每次得到 r,更新 (a[i]b[i]r)x[i]
易得 题目求最大值时,我们排序后取大的 k 个数
题目求最小值时,我们排序后取小的 k 个数

二分:

#include <iostream>#include <cstring>#include <string>#include <cstdio>#include <algorithm>using namespace std;typedef long long ll;#define LL long long#define mem(s,t) memset(s,t,sizeof s)#define pb push_back#define inf 0x3f3f3f3fconst ll MAXN = 2e4+10;const double eps=1e-6;struct node{    int a,b;    double v;}p[MAXN];bool cmp(node x,node y){    return x.v>y.v;}int main(){    int n,m,k;    while(~scanf("%d%d",&n,&m) && n+m){        k=n-m;        for(int i=0;i<n;i++) scanf("%d",&p[i].a);        for(int i=0;i<n;i++) scanf("%d",&p[i].b);        double l=0,r=100,mid;        while(l+eps<r){            mid=(l+r)/2;            for(int i=0;i<n;i++){                p[i].v=p[i].a-mid*p[i].b;            }            sort(p,p+n,cmp);            double sum=0;            for(int i=0;i<k;i++){                sum+=p[i].v;            }            if(sum>0) l=mid;            else r=mid;        }        printf("%.0f\n",l*100);    }}

Dinkelbach法

类似二分,由于 f(r)是可以逐次逼近 0 的.
上次得到答案 r ,用 r 去计算 (a[i]rb[i]) ,排序后,取前 k 个利用原始公式计算出结果 R
当 R 和 r 相等(eps)时,r 即为最终答案,否则迭代下去计算.

学习到的牛顿迭代法,需要一个估计值,以及要求迭代过程是收敛的,在本题里表现为:
每次确定一个 r,都可以通过表达式接着确定下一个更精确的 R
%%%

#include <iostream>#include <cstring>#include <string>#include <cstdio>#include <algorithm>#include <cmath>using namespace std;typedef long long ll;#define LL long long#define mem(s,t) memset(s,t,sizeof s)#define pb push_back#define inf 0x3f3f3f3fconst ll MAXN = 1e4+10;const double eps=1e-6;struct node{    double a,b;    double v;}p[MAXN];bool cmp(node x,node y){    return x.v>y.v;}int main(){    int n,m,k;    while(~scanf("%d%d",&n,&m) && n+m){        k=n-m;        for(int i=0;i<n;i++) scanf("%lf",&p[i].a);        for(int i=0;i<n;i++) scanf("%lf",&p[i].b);        double r=0,R=0;        double x=0,y=0;        for(int i=0;i<k;i++) x+=p[i].a,y+=p[i].b;        r=x/y;        while(fabs(R-r)>=eps){            r=R;            for(int i=0;i<n;i++){                p[i].v=p[i].a-r*p[i].b;            }            sort(p,p+n,cmp);            double x=0,y=0;            for(int i=0;i<k;i++){                x+=p[i].a;                y+=p[i].b;            }            R=x/y;        }        printf("%.0f\n",r*100);    }}//POJ里用int和db混用会T 大概是强制转换&&迭代次数太多了??全用db就过了

需要注意的是,很多01分数规划都是要求保留整数,这时要看清是四舍五入(%.0f)还是截断整数(显式转换为int)

原创粉丝点击