POJ2018 Best Cow Fences - 二分【带长度限制的最大平均数子段】

POJ2018 Best Cow Fences

Sol:

题目要求一个平均数最大且长度不小于L的子段,并输出平均数*1000的整数部分(直接截取而非舍入)

以下思路来自PKU李煜东神犇的《算法竞赛进阶指南》

我们将问题转化一下:相当于将序列中每个数都减去同一个值,且最大子段和非负(即能够找到一个子段,使得该子段中所有数都大于等于这个减去的数)。可以发现,这个数具有单调性:对于同一个序列,减去一个大的数后最大子段和非负,则减去一个较小的数后最大子段和必定非负。因此我们考虑二分这个数。
若没有子段长度不小于L的限制,我们可以O(n)扫一遍整个序列,每次将当前元素加进备选答案,遇到负数就用备选答案更新最终答案,然后将备选答案清空。
对于有子段长度限制的情况,我们将子段和转化为前缀和的形式,则答案为\(\max_{1 \leq i \leq n}(sum_i-\min_{1 \leq j \leq i-L}(sum_j))\)
观察到每次\(sum_j\)的决策只会从\(1 \leq j \leq i-L\)\(1 \leq j \leq i-L+1\),即每次只有一个元素加入决策集合,而没有元素从集合中删除,因此我们可以记录一个minval,表示当前决策集合的最优决策,然后O(n)枚举每一个元素,先更新minval,然后用\(sum_i-minval\)更新ans。若ans非负,说明当前二分的平均数mid还有增大的可能,否则只能减小。
时间复杂度为\(O(nlogn)\)

Tips:

1.思路: 子段和 -> 前缀和相减
2.思路: 求平均数的做法
3.%.lf 自动进行四舍五入 而%d int()则直接截取整数部分。
4.实数域上的二分写法

double l=-(1<<30),r=(1<<30),mid;
double eps=1e-5;//一般精度eps设置为1e-(精确位数+2)
while(r-l>eps){
    mid=(l+r)/2;
    if(check(mid)) l=mid; 
    else r=mid;
}

5.INF开到(1<<30) -INF开到-(1<<30) 若-INF开到0,可能在出现负数解的情况下得到错误答案。

AC CODE:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100000 + 10;
int read(){
    int x=0,f=1;char ch=' ';
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^'0');ch=getchar();}
    return x*f;
}
int n,L;
double a[N];
double tp[N];
double s[N];
bool check(double x){
    for(int i=1;i<=n;i++) tp[i]=a[i]-x;
    s[0]=0;for(int i=1;i<=n;i++) s[i]=s[i-1]+tp[i];
    double ans=-(1<<30),minval=(1<<30);
    for(int i=L;i<=n;i++){
        minval=min(minval,s[i-L]);
        ans=max(ans,s[i]-minval);
    }
    if(ans>=0) return true;
    else return false ;
}
int main(){
//  freopen("data.in","r",stdin);
//  freopen("sol.out","w",stdout);
    n=read(),L=read();
    for(int i=1;i<=n;i++){
        scanf("%lf",&a[i]);
    }
    double l=-(1<<30),r=(1<<30),mid;
    double eps=1e-5;
    while(r-l>eps){
        mid=(l+r)/2;
        if(check(mid)) l=mid; 
        else r=mid;
    }
    printf("%d",int(1000*r));
    return 0;
}

转载于:https://www.cnblogs.com/Loi-Brilliant/p/9763826.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值