题解:P8424 [JOI Open 2022] 跷跷板(Seesaw)

· · 题解

设初始重心为 $P$,对于一个答案 $[l, r]$,设 $L_1=P-l$,则 $r$ 大致是关于 $L_1$ 增加而减小的,但是答案 $L_1+r-P$ 却被发现不具有凸性或单峰等性质。 但是一个 $L_1$ 代表 $r^{\prime}<r$ 均(可以被认为)合法。这意味着,$\left\{L_1, r\right\}$ 应该成阶梯状。根据 Quickhull 的思想,只需要把阶梯处的凹位置全都遍历一边就可以了。这可以通过二分找下一个 $r$ 不同的位置来实现。设有 $K$ 个凹点,那么时间复杂度是 $O(n K \log n)$。随机数据下,$n=2 \times 10^5$ 大概 $K=70$。 ```cpp #include<bits/stdc++.h> using namespace std; #define db double const int maxn=2e5+5; int n; db a[maxn],P,s[maxn],ans=0; inline db Calc(db L1){ int l=1,r=n; db mx=P; while(r-l>0){ while(r-l+1>1&&(s[r-1]-s[l-1])>(P-L1)*(r-l))--r; if(r-l+1==1)break; l++; mx=max(mx,(s[r]-s[l-1])/(r-l+1)); } return mx-P; } priority_queue<pair<db,db> > Q; signed main(){ // freopen("balance.in","r",stdin); // freopen("balance.out","w",stdout); cin>>n; for(int i=1;i<=n;i++)cin>>a[i],P+=a[i],s[i]=s[i-1]+a[i]; if(n==2){ printf("%.10lf\n",(a[2]-a[1])/2.0); return 0; } P/=n; db cur=0,curc=Calc(0),ans=curc; int cnt=0; while(1){ ++cnt; db L=cur,R=1e9,res=-1,cres; while(Q.size()&&(fabs(curc-Q.top().second)<1e-6||(-Q.top().first)<=cur))Q.pop(); if(Q.size())R=-Q.top().first; while(R-L>max(L,1.0)*1e-9){ db mid=(L+R)/2.0,r=Calc(mid); if(R-L>1e-3)Q.push({-mid,r}); if(fabs(r-curc)>1e-6)res=mid,R=mid,cres=r; else L=mid; } if(res!=-1)ans=min(ans,cres+res),cur=res,curc=cres; else break; } // cout<<cnt<<endl; printf("%.10lf\n",ans); return 0; }```