[USACO14MAR] Sabotage 二分答案 分数规划

最终答案的式子:
\[\frac{sum-sum[l,r]}{n-len[l,r]}\le ans\]
转换一下:
\[sum[1,l-1]+sum[r+1,n]\le ans*(len[l,l-1]+len[r+1,n])\\\sum (a[j]-ans)\le 0 \\(j\in[1,l-1],[r+1,n])\]
这里我们先都减去\(ans\),然后求一个前缀和、后缀和、在\(i\)点最小的前缀和,在\(i\)点最小的后缀和,\(O(n)\)枚举断点(也不算断点)如果存在则return 1

#include <cstdio>
#include <algorithm>
#define MAXN 100010
using namespace std;
int n;
int m[MAXN];
double psum[MAXN],lsum[MAXN],psum_min[MAXN],lsum_min[MAXN],a[MAXN];
const double eps=1e-6;
inline bool check(double x){
    for(int i=0;i<=n+1;++i) psum_min[i]=lsum_min[i]=1e9;
    for(int i=1;i<=n;++i) a[i]=(double)m[i]-x;
    for(int i=1;i<=n;++i) psum[i]=psum[i-1]+a[i], psum_min[i]=min(psum_min[i-1], psum[i]);
    for(int i=n;i;--i) lsum[i]=lsum[i+1]+a[i], lsum_min[i]=min(lsum_min[i+1], lsum[i]);
    for(int i=1;i<n-1;++i)
        if(psum_min[i]+lsum_min[i+2]<=0) return 1;
    return 0;
}
int main(){
    scanf("%d", &n);
    for(int i=1;i<=n;++i) scanf("%d", &m[i]);
    double l=1,r=10000,ans=-1;
    while(r-l>eps){
        double mid=(l+r)/2;
        if(check(mid)) r=mid,ans=mid;
        else l=mid;
    }
    printf("%.3lf", ans);
}
12-30 23:57