POJ 3111 二分解决最大化平均值问题AND高效牛顿迭代法

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 = {i1, i2, …, 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

题意:这道题目和poj2976差不多,就是k这一次是需要选择的个数了,然后求出最大结果是由哪一些组成的,最后输出对应的编号就好

这一道题是可以用二分枚举求解的,但是比较耗时,我就是T掉一次,也不知道为什么,我感觉可能是爆栈或者是程序太繁琐

先上一波TLE的代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define lep(i,l,r) for(int i=l;i>=r;i--)
const int N = 3e5 + 10;
const int MOD = 0x3f3f3f3f;
int k,n;
bool vis[N];
double ans,temp;
struct node{
    double a,b;
    int id;
}num[N];
struct node1{
    double val;
    int point;
    bool operator < (const node1 &a)const{
        return val>a.val;
    }
}y[N];
int Pt[N];
bool solve(double x)
{
    rep(i,1,n)
    y[i].val=num[i].a-x*num[i].b,y[i].point=num[i].id;
    sort(y+1,y+1+n);
    double sum=0;
    for(int i=1;i<=k;i++) sum+=y[i].val,Pt[i]=y[i].point;
    return sum>=0;
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif // ONLINE_JUDGE
    scanf("%d%d",&n,&k);
            rep(i,1,n) scanf("%lf%lf",&num[i].a,&num[i].b),num[i].id=i;
            double l=0,r=MOD;
            for(int i=1;i<=100;i++)
            {
                double mid=(l+r)/2;
                if(solve(mid)) l=mid;
                else r=mid;
            }
            rep(i,1,k)
            printf("%d ",Pt[i]);
    return 0;
}

然后我就将上面的y数组修改去掉一下,就可以AC,当然,你也可以将控制精度的for循环改成50也能AC

下面是AC的代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define lep(i,l,r) for(int i=l;i>=r;i--)
const int N = 3e5 + 10;
const int MOD = 0x3f3f3f3f;
int k,n;
bool vis[N];
double ans,temp;
struct node{
    double a,b;
    double val;
    int id;
    bool operator < (const node &a)const{
        return val>a.val;
    }
}num[N];
int Pt[N];
bool solve(double x)
{
    rep(i,1,n)
    num[i].val=num[i].a-x*num[i].b;//,y[i].point=num[i].id;
    sort(num+1,num+1+n);
    double sum=0;
    for(int i=1;i<=k;i++) sum+=num[i].val,Pt[i]=num[i].id;
    return sum>=0;
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif // ONLINE_JUDGE
    scanf("%d%d",&n,&k);
            rep(i,1,n) scanf("%lf%lf",&num[i].a,&num[i].b),num[i].id=i;
            double l=0,r=MOD;
            for(int i=1;i<=100;i++)
            {
                double mid=(l+r)/2;
                if(solve(mid)) l=mid;
                else r=mid;
            }
            rep(i,1,k)
            printf("%d ",Pt[i]);
    return 0;
}

还有一种更加高效的方法--牛顿迭代法,这一个可以参考一下牛顿迭代的应用https://blog.csdn.net/wangxiaojun911/article/details/18203333?utm_source=blogxgwz0

下面的解析转载大佬https://blog.csdn.net/yew1eb/article/details/38730447

先取前k个元素算出S0 =∑(vi/wi) 作为初始值

然后对每一个元素(n个)求yi=vi-s0*wi

对yi从大到小排序,取前k个元素算出S,

重复上面的运算(每次循环后把S的值赋给S0,然后新一轮循环时S有通过S0计算出来),直到fabs(S-S0)<=eps,满足精度要求。


正确性证明:

证明其正确性,只要证明每次迭代的S都比上一次的大即可,也即迭代过程中S是单调递增的,因为给定的是有限集,故可以肯定,S必存在最大值,即该迭代过程是收敛的。下面证明单调性:

假设上轮得到的S1,则在n个元素中必存在k个元素使S1=∑(vi/wi),变形可得到∑vi-S1*∑wi=0,

现对每个元素求yi=vi-S1*wi,可知必存在k个元素使∑yi=∑vi-s1*∑wi=0, 所以当我们按y排序并取前k个元素作为求其∑y时,其∑y>=0,

然后对和式变形即可得到S1=((∑v-∑y)/∑w)<=(∑v/∑w)=s2,即此迭代过程是∑y是收敛的,当等号成立时,此S即为最大值。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define lep(i,l,r) for(int i=l;i>=r;i--)
const int N = 3e5 + 10;
const int MOD = 0x3f3f3f3f;
int k,n;
bool vis[N];
double ans,temp;
struct node{
    double a,b;
    double val;
    int id;
    bool operator < (const node &a)const{
        return val>a.val;
    }
}num[N];
int Pt[N];
double solve()
{
    double sum1=0,sum2=0;
    rep(i,1,k)
    sum1+=num[i].a,sum2+=num[i].b;
    return sum1/sum2;
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif // ONLINE_JUDGE
    scanf("%d%d",&n,&k);
            rep(i,1,n) scanf("%lf%lf",&num[i].a,&num[i].b),num[i].id=i;
            double l=0,r=MOD;
            r=solve();
            while(r-l>1e-8){
                l=r;
                rep(i,1,n)
                num[i].val=num[i].a-r*num[i].b;
                sort(num+1,num+1+n);
                r=solve();
            }
            rep(i,1,k)
            printf("%d ",num[i].id);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/c___c18/article/details/83378533