poj 2018_Best Cow Fences (求数列中一个字段和最大问题,字段的长度不小于L)

想要理解这个问题我们需要先掌握几个要点:

1、对于一个序列,求一个字段它的和最大,没有“长度不小于L的限制”问题。

问题分析见:https://blog.csdn.net/qq_35937273/article/details/82799942

2、对于一个序列,求一个字段它的和最大,字段的长度不小于L的问题。

字段和可以转化成为前缀和相减的形式,也就是说sumi=(a1+a2+...+ai)

因此:max(i-j>=L){aj+aj+1+aj+2+...+ai}=max(L<=i<=n){sumi-min(0<=j<=i-L){ sumj } }  仔细理解一下。

仔细观察上式子,随着i的增长,j的取值范围0~i-L每次只会增大1.换言之,每次只会有一个新的取值进入min{sumj}的候选集合,所以我们没有必要每次循环枚举j ,只需要用一个变量记录当前最小值,每次与新的取值sum(i-L)取min就可以了。

double ans=-1e10;
double min_val=1e10;
for(int i=L;i<=N;i++)
{
    min_val=min(min_val,sum[i-L]);
    ans=max(ans,sum[i]-min_val);
}

                                                                                                                                                            --摘自《算法竞赛进阶指南》

3、对于一个单调的序列,潜在的含义有它们的平均值为中点值。

其实对于问题,我们可以进行转化,如果我们把数列中每一个数都减去二分的值,就转化为判定“是否存在一个长度不小于L的子段,子段和非负”。这样的问题结合刚才的分析就容易求解了:

#include 
#include 
#include 
#include 
using namespace std;
//前提 要知道对于一个按序递增数列来说其平均值就是数列的中点值
const int maxn=1e6+5;
double a[maxn],b[maxn],sum[maxn];
int main()
{
    int n,L;scanf("%d%d",&n,&L);
    for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
    double eps=1e-5,l=0,r=2001;  //对于二分还要注意的一点就是边界的问题,二分有时候取不到边界
    while(r-l>eps)
    {
        double mid=(l+r)/2; //也就是求平均值
        for(int i=1;i<=n;i++) b[i]=a[i]-mid;
        for(int i=1;i<=n;i++) sum[i]=sum[i-1]+b[i];
        double ans=-1e10;
        double min_val=1e10;
        for(int i=L;i<=n;i++)
        {
            min_val=min(min_val,sum[i-L]);
            ans=max(ans,sum[i]-min_val);
        }
        if(ans>=0) l=mid;else r=mid;
    }
    printf("%d\n",int(r*1000));
    return 0;
}

 

你可能感兴趣的:(poj)