1. 程式人生 > >poj 2018_Best Cow Fences (求數列中一個欄位和最大問題,欄位的長度不小於L)

poj 2018_Best Cow Fences (求數列中一個欄位和最大問題,欄位的長度不小於L)

想要理解這個問題我們需要先掌握幾個要點:

1、對於一個序列,求一個欄位它的和最大,沒有“長度不小於L的限制”問題。

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 <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
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;
}