【POJ 2018】Best Cow Fences

题目描述

求一个长度为 N 的序列中长度不超过 F 的子段,使得这个子段的平均值最大,输出这个平均值的 1000 倍。

算法分析

最大化平均值,二分答案,将序列中的每个元素减去当前判断的平均值,则当前判断的结果就是是否存在一个新序列中的子段,使得这个子段的和不小于 0

将子段拆成前缀和的形式,枚举右端点,由于可选范围递增,再维护一个变量用来储存当前可选范围中前缀和最小的右端点,统计分别以每个点为右端点所形成的最大子段和,判断它是否大于或等于 0

实现时不知道为什么,限定二分循环次数会 Wrong Anwer,只能手动设置精度,还有最后要输出右端点 r,有没有人知道为什么啊?求助。

【Update】找到原因啦!!!
可能是因为卡精度,如果在最后输出的时候加上一些精度的处理就能AC了(保留三位小数所以 eps 10 5 ),代码见下面的代码二。

代码实现

代码一:

#include <cstdio>
#include <climits>
#include <algorithm>
const int maxn=100005;
int n,f;double arr[maxn],temp[maxn],sum[maxn];
bool check(double num) {
    for(int i=1;i<=n;++i) {
        temp[i]=arr[i]-num;
        sum[i]=sum[i-1]+temp[i];
    }
    double mininum=1e10,ans=-1e10;
    for(int i=f;i<=n;++i) {
        mininum=std::min(mininum,sum[i-f]);
        ans=std::max(ans,sum[i]-mininum);
    }
    return ans>=0;
}
int main() {
    scanf("%d%d",&n,&f);
    double l=1e10,r=-1e10;
    for(int i=1;i<=n;++i) {
        scanf("%lf",&arr[i]);
        l=std::min(l,arr[i]);
        r=std::max(r,arr[i]);
    }
    while(r-l>1e-5) {
        double mid=(l+r)/2;
        if(check(mid)) l=mid;
        else r=mid;
    }
    printf("%d\n",int(r*1000));
    return 0;
}

代码二:

#include <cstdio>
#include <climits>
#include <algorithm>
const int maxn=100005;
int n,f;double arr[maxn],temp[maxn],sum[maxn];
bool check(double num) {
    for(int i=1;i<=n;++i) {
        temp[i]=arr[i]-num;
        sum[i]=sum[i-1]+temp[i];
    }
    double mininum=1e10,ans=-1e10;
    for(int i=f;i<=n;++i) {
        mininum=std::min(mininum,sum[i-f]);
        ans=std::max(ans,sum[i]-mininum);
    }
    return ans>=0;
}
int main() {
    scanf("%d%d",&n,&f);
    double l=1e10,r=-1e10,mid;
    for(int i=1;i<=n;++i) {
        scanf("%lf",&arr[i]);
        l=std::min(l,arr[i]);
        r=std::max(r,arr[i]);
    }
    for(int i=0;i<100;++i) {
        mid=(l+r)/2;
        if(check(mid)) l=mid;
        else r=mid;
    }
    printf("%d\n",int(l*1000+1e-5));
    return 0;
}

猜你喜欢

转载自blog.csdn.net/whz2018/article/details/81222965