1. 程式人生 > >洛谷【P2115】[USACO14MAR]破壞Sabotage

洛谷【P2115】[USACO14MAR]破壞Sabotage

turn href 減少 -m span .org check 破壞 getchar()

我對二分的理解:https://www.cnblogs.com/AKMer/p/9737477.html

題目傳送門:https://www.luogu.org/problemnew/show/P2115

對於我們要求的一個“最小平均值”,我們可以通過二分來得到。對於我們二分的那個平均值,我們令每一個數全部減去它,然後這時刪掉“最大子段和”就是最優策略。

假設減完平均值之後的數列和為\(sum\),那麽我們二分的平均值為\(ave\),要使得平均值降到\(ave\)以下,整個數列的權值和至少要減少\(sum\)。也就是說,減得越多越好,只要能減到\(sum\)那麽多,那麽最低平均值必然小於等於\(ave\)。因為減完之後\(sum<=0\)

,也就是平均值乘以\(n\)小於等於\(ave*n\),也就是平均值會小於等於\(ave\)

總結:如果最大子段和要大於等於刪完平均值之後的數列和,那麽說明我可以通過刪掉這一段使得平均值降低到我們二分的值或那個值以下。

時間復雜度:\(O(nloga)\)

空間復雜度:\(O(n)\)

代碼如下:

#include <cstdio>
#include <algorithm>
using namespace std;

const double eps=1e-6;
const int maxn=1e5+5;

int n;
int a[maxn];
double b[maxn],sum[maxn];

int read() {
    int x=0,f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
    return x*f;
}

bool check(double ave) {
    for(int i=1;i<=n;i++)
        b[i]=a[i]-ave,sum[i]=sum[i-1]+b[i];//求前綴和
    double mn=sum[1],fake=-1e9;//mn存1~i-1的前綴和最小值,fake存最大子段和
    for(int i=2;i<n;i++) {
        fake=max(fake,sum[i]-mn);//sum[i]-1~i-1的前綴和最小值就是以i結尾的最大子段和
        mn=min(mn,sum[i]);//更新mn
    }
    if(fake>=sum[n])return 1;//判斷平均值是否可以小於等於ave
    return 0;   
}

int main() {
    n=read();double l=0,r=0;
    for(int i=1;i<=n;i++)
        a[i]=read(),r=max(r,(double)a[i]);
    while(l+eps<r) {
        double mid=(l+r)/2;
        if(check(mid))r=mid;
        else l=mid;//實數二分
    }
    printf("%.3lf\n",r);//平均值肯定不能比r小了。
    return 0;
}

洛谷【P2115】[USACO14MAR]破壞Sabotage