题解 P8424 【[JOI Open 2022] 跷跷板(Seesaw)】
容易发现每次拿掉最左或最右侧的砝码相当于将原来有砝码的区间
直接考虑最值是不方便的,注意到最初一定有一个
直接考虑区间左右端点的两种移动方式也是不方便的,于是我们假装去掉这个限制,则我们希望得到每移动一次后
不难想到
贪心地,我们希望每步都取到该步对应的两个区间之一。事实上,注意到双指针维护区间时一定是在上一步的两个区间的基础上将左端点加一或右端点减一,于是感性理解一下,我们每步一定可以取到其对应的两个区间之一。
于是我们预处理出每两个区间对应的 pair 中,按照其中一维升序,相同时再按另一维升序,最后维护没有优先升序的那一维的后缀最大值即可。时间复杂度为
代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
typedef struct Pair_tag {
double a;
double b;
Pair_tag(){}
Pair_tag(double a_, double b_){
a = a_;
b = b_;
}
} Pair;
ll sum[200007];
Pair pr[200007];
bool operator <(const Pair a, const Pair b){
if (a.a != b.a) return a.a < b.a;
return a.b < b.b;
}
int main(){
int n;
double base, suf = 0.0, ans = 1e9;
cin >> n;
for (register int i = 1; i <= n; i++){
int a;
cin >> a;
sum[i] = sum[i - 1] + a;
}
base = 1.0 * sum[n] / n;
for (register int i = 1, j = 1, x = n; i < n; i++){
int len = n - i;
j++;
if (1.0 * (sum[x] - sum[j - 1]) / len > base){
j--;
x--;
}
pr[i] = Pair(base - 1.0 * (sum[x] - sum[j - 1]) / len, 1.0 * (sum[x + 1] - sum[j]) / len - base);
}
sort(pr + 1, pr + n);
for (register int i = n - 1; i >= 1; i--){
ans = min(ans, pr[i].a + suf);
suf = max(suf, pr[i].b);
}
printf("%.10lf", ans);
return 0;
}