P8424 [JOI Open 2022] 跷跷板(Seesaw)

发布时间 2023-11-01 21:59:13作者: Thunder_S

Description

一根长度为 \(10^9\) 的直杆从左到右水平放置。你可以忽略这根杆的重量。共有 \(N\) 个砝码挂在这根杆上,每个砝码的质量为一单位。这 \(N\) 个砝码的位置两两不同。第 \(i(1 \leq i \leq N)\) 个砝码的位置为 \(A_i\) 。即,第 \(i\)个砝码到直杆最左端的距离为 \(A_i\)

最开始,我们有一个宽度为 \(w\) 的箱子。我们可以把这根杆子放在箱子上,支摚起杆从 \(l\)\(r(0 \leq l<\) \(r \leq 10^9\) ) 的部分 (包括两端),即,从杆上位置为 \(l\) 到杆上位置为 \(r\) 的区间。这里需要满足 \(r=l+w\)。之后我们不可以改变 \(l\)\(r\) 的值。

接下来,我们去掉挂在杆上最左端或最右端的砝码。我们需要重复这个操作 \(N-1\) 次。在这个过程中,包括初始状态和最终状态,挂在杆上的所有砝码重心都需要保持在 \(l\)\(r\) 之间(包括两端)。如果杆上挂有 \(m\) 个砝码,位置分别为 \(b_1, b_2, \ldots, b_m\) ,那么重心位置为 \(\frac{b_1+b_2+\cdots+b_m}{m}\)

给定 \(N\) 和这 \(N\) 个砝码的位置 \(A_1, A_2, \ldots, A_N\) ,写一个程序计算箱子的最小可能宽度 \(w\)

\(2\le N\le 2\times 10^5,0\le A_1<A_2<\cdots<A_N\le 10^9\)

Solution

对于最后的重心区间,\(\dfrac{sum_n}{n}\) 是一定会被包含的。那么我们考虑从 \(\dfrac{sum_n}{n}\) 出发,看看它最左能扩到哪,最右能扩到哪。记 \(base=\dfrac{sum_n}{n}\)

你考虑对于操作 \(i\) 次后求两个下标区间 \([l_1,r_1]\)\([l_2,r_2]\),分别表示对应的重心是在 \(base\) 左边最右和右边最左的。那么有 \(l_2=l_1+1,r_2=r_1+1\)。因为既然是左边最右的,那么向右移动一位,重心就一定会越过 \(base\),从而得到右边最左。

考虑怎么求 \(i\) 次操作后的 \([l_1,r_1]\)。记 \(i\) 次操作完的 \([l_1,r_1]\)\([l_i,r_i]\),那么区间 \([l_{i-1},r_{i-1}-1]\) 所对应的重心一定在 \(base\) 左边。而 \([(l_{i-1}+1)+1,r_{i-1}]\) 对应的重心一定在 \(base\) 右边。因此 \([l_i,r_i]\) 可能是 \([l_{i-1},r_{i-1}+1]\) 或者 \([l_{i-1}+1,r_{i-1}]\),算一下 \([l_{i-1}+1,r_{i-1}]\) 对应的重心即可。

现在求完了所有的 \([l_i,r_i]\)。贪心地,我们希望每次操作后要么是 \([l_i,r_i]\),要么是 \([l_i+1,r_i+1]\)。而事实上,从 \([l_{i-1},r_{i-1}]\) 是一定可以通过取左右端点的做法走到 \([l_i,r_i]\) 的。而对于 \([l_{i-1}+1,r_{i-1}+1]\) 同样可以到达 \([l_i+1,r_i+1]\)

考虑对于 \(i\) 次操作后,求出最小的左偏移量和右偏移量。显然就是 \(base\) 减去 \([l_i,r_i]\) 的重心和 \([l_i+1,r_i+1]\) 的重心减去 \(base\)。枚举最小的左偏移量,那么对于左偏移量超过枚举的那些,就一定要选右偏移,那么求一个最大值即可。

因此排序,以左偏移量为第一关键字,右偏移量为第二关键字升序排序。然后对于每个左偏移量,就用其加上后缀右偏移量最大值取更新答案。

Code

#include<cstdio>
#include<algorithm>
#define N 200005
#define inf 1e9
#define ll long long
#define db double
using namespace std;
int n,a[N];
ll sum[N];
db base,ans=inf,mx;
struct node
{
    db lv,rv;
}v[N];
bool cmp(node x,node y)
{
    if (x.lv!=y.lv) return x.lv<y.lv;
    return x.rv<y.rv;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i)
        scanf("%d",&a[i]),sum[i]=sum[i-1]+(ll)a[i];
    base=1.0*sum[n]/n;
    for (int i=1,l=1,r=n;i<n;++i)
    {
        l++;
        if (1.0*(sum[r]-sum[l-1])/(r-l+1)>base) l--,r--;
        v[i].lv=base-1.0*(sum[r]-sum[l-1])/(r-l+1);
        v[i].rv=1.0*(sum[r+1]-sum[l])/(r-l+1)-base;
    }
    sort(v+1,v+n,cmp);
    for (int i=n-1;i;--i)
        ans=min(ans,v[i].lv+mx),mx=max(mx,v[i].rv);
    printf("%.9lf\n",ans);
    return 0;
}