poj 3111 K Best 【0-1分数规划】 【二分 or Dinkelbach迭代】

K Best
Time Limit: 8000MS Memory Limit: 65536K
Total Submissions: 7154 Accepted: 1875
Case Time Limit: 2000MS Special Judge

Description

Demy has n jewels. Each of her jewels has some value vi and weight wi.

Since her husband John got broke after recent financial crises, Demy has decided to sell some jewels. She has decided that she would keep k best jewels for herself. She decided to keep such jewels that their specific value is as large as possible. That is, denote the specific value of some set of jewels S = {i1i2, …, ik} as

.

Demy would like to select such k jewels that their specific value is maximal possible. Help her to do so.

Input

The first line of the input file contains n — the number of jewels Demy got, and k — the number of jewels she would like to keep (1 ≤ k ≤ n ≤ 100 000).

The following n lines contain two integer numbers each — vi and wi (0 ≤ vi ≤ 106, 1 ≤ wi ≤ 106, both the sum of all vi and the sum of all wi do not exceed 107).

Output

Output k numbers — the numbers of jewels Demy must keep. If there are several solutions, output any one.

Sample Input

3 2
1 1
1 2
1 3

Sample Output

1 2


题意:给你N个珠宝已经每个珠宝的价值和重量,你只能取其中K个。问你取哪几个珠宝使sigma(v[i])/ sigma(w[i])最大。




方法一:构造函数 sigma(t[i]) = sigma(v[i]) - sigma(w[i]) * o。 然后 二分o值


这道题没有那么高精度,用整型就足够了。 我用的double跑了6s。。。


AC代码:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 100000+10
#define eps 1e-8
using namespace std;
struct Node
{
    double v, w, t;
    int id;
};
Node num[MAXN];
int N, K;
bool cmp(Node a, Node b)
{
    return a.t > b.t;
}
bool judge(double o)
{
    for(int i = 0; i < N; i++)
        num[i].t = num[i].v - o * num[i].w;
    sort(num, num+N, cmp);
    double sum = 0;
    for(int i = 0; i < K; i++)//取前K个最大的
        sum += num[i].t;
    return sum >= 0;
}
int main()
{
    while(scanf("%d%d", &N, &K) != EOF)
    {
        double r = 0;
        for(int i = 0; i < N; i++)
        {
            scanf("%lf%lf", &num[i].v, &num[i].w);
            num[i].id = i + 1;
            r = max(r, num[i].v / num[i].w);
        }
        double l = 0;
        while(r - l >= eps)
        {
            double mid = (l + r) / 2;
            if(judge(mid))
                l = mid;
            else
                r = mid;
        }
        for(int i = 0; i < K; i++)
        {
            if(i > 0) printf(" ");
            printf("%d", num[i].id);
        }
        printf("\n");
    }
    return 0;
}

方法二:Dinkelbach迭代 1922ms   【速度比二分要快,精度没有二分高】


刚学习,为了加深印象,写了一点自己的见解,不对的地方欢迎指正。


我们设x为当前选取K个珠宝得到的最优解。

1,首先任取K个珠宝的均值为x的初始值,然后对每个珠宝计算其t = v - x * w 的值,根据t值升序排序。

2,把前K个作为本次要选取的珠宝,计算它们的均值 = ∑v / ∑w。把本次的 x 值 赋给x0,再令x = ∑v / ∑w。

3,判断x和x0的大小,直到选取K个珠宝所计算出的 x 不再大于 x0(就是上一次的x值)停止。


为了验证上述过程的正确性,我们需要讨论x的单调性。只要我们能够证明 上述过程中的x是单调递增的就可以了。


x的单调性: 我们先考虑当前K个珠宝所得到的x,必然有∑v - x * ∑w = 0,即∑t=0。我们根据这个x值计算出所有的 t[i] 值,对于新的 t[i],在升序排列后,我们若取前K个,则必然有∑t >= 0, 即∑v / ∑w >= x。

我们可以证出 —— x 是单调递增的。因此当下一次计算出的x值不再增加时,则可以说明当前的x已经是最大值。



AC代码:


#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 100000+10
#define eps 1e-8
using namespace std;
struct Node
{
    double v, w, t;
    int id;
};
Node num[MAXN];
int N, K;
bool cmp(Node a, Node b)
{
    return a.t > b.t;
}
int main()
{
    while(scanf("%d%d", &N, &K) != EOF)
    {
        double r = 0;
        for(int i = 0; i < N; i++)
        {
            scanf("%lf%lf", &num[i].v, &num[i].w);
            num[i].id = i + 1;
        }
        double x0;
        double sumv = 0, sumw = 0;
        for(int i = 0; i < K; ++i)
            sumv += num[i].v, sumw += num[i].w;
        double x = sumv / sumw;//初始x取 任意K个珠宝的均值
        while(1)
        {
            for(int i = 0; i < N; i++)//每次由当前的x值 继续推导 看是否有比x大的最优值
                num[i].t = num[i].v - x * num[i].w;
            sort(num, num+N, cmp);
            sumv = 0, sumw = 0;
            for(int i = 0; i < K; i++)
            {
                sumv += num[i].v;
                sumw += num[i].w;
            }
            x0 = x, x = sumv / sumw;//x赋值给x0,x更新  下一步判断x更新后的值是否大于以前的值x0
            if(x - x0 < eps)//直到x的值不再增加 最大值临界
                break;
        }
        for(int i = 0; i < K; i++)
        {
            if(i > 0) printf(" ");
            printf("%d", num[i].id);
        }
        printf("\n");
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值