算法进阶指南二分章节的两道题

A.题意:给定个数为N的数列,从中挑一些不小于L的连续子段,求这些子段当中的数平均值最大是多少?

思路:二分平均值转化为判定。我们直接去求这个>=L的子段当中的最大平均值比较难求。所以我们可以用二分的方法枚举mid,然后在判定这个mid是否合法。

判定方法为,是否存在一个长度大于L的连续子段它的平均值>=mid。如果不存在,说明以mid为平均值取大了。则取r=mid。

对平均值的处理有一种特殊方法,另每一个数都减去mid,则判定方法就转化为是否存在一个长度>=L的连续子段,使得每个数的和非负(最大的都>=0,则 一定存在)。

求某一连续子段的方法,利用前缀和来求解,eg sum[ i ] - sum [ j ]就是求  j 到 i 的连续子段和

另外用双指针来求>=L的区间的最大和。因为要保证长度是大于L的 所以i要从L开始往后递增,而  j  从   i-L  开始往前递减, sum[  i ]  -  sum [ j ]就保证了一个大于L的区间。但是由于i 从L往后每递增一个, j 的可选择空间也就增加一个,j的初始空间只有一个选择,而这道题判定方法问存不存在,所以要求我们求连续子段的最大的和,于是我们只需要保存前  i - L 当中最小的那个前缀和就可以了。

下面上代码:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=1e5+6;
const double eps=1e-6;
int n,L;
double cow[maxn];
double sum[maxn];
double b[maxn];

bool check(double ave)
{
    for(int i=1;i<=n;i++)
        b[i]=cow[i]-ave;
        
        sum[0]=0;
        for(int i=1;i<=n;i++)
        sum[i]=sum[i-1]+b[i];
        
        double ans=-1e10;
        double min_v=0;
        for(int i=L;i<=n;i++)
        {
            min_v=min(min_v,sum[i-L]);
            ans=max(ans,sum[i]-min_v);
        }
        if(ans>=0)
        return true;
        else
        return false;
}



int main()
{
    cin>>n>>L;
    for(int i=1;i<=n;i++)
    //scanf("%lf",&cow[i]);
    cin>>cow[i];
    
    double l=0,r=2000;
    while(r-l>eps)
    {
        double mid=(r+l)/2;
        if(check(mid)) l=mid;
        else r=mid;
    }
    
    printf("%d
",(int)(r*1000));
    return 0;
 } 
原文地址:https://www.cnblogs.com/rainyskywx/p/10349568.html