题解:P12503 「ROI 2025 Day1」索契游乐园
不错的函数最优化练习题。考验选手卡常技巧。
首先,对于一个选手来说向左走和向右走是独立的。有两种策略,一个是向左来回走一趟然后向右,另一种是向右来回一趟再向左。直接把来回的权值设成
于是经过上述转化,我们只需要平移坐标然后考虑从
记录一些前后缀和可以
这个式子是一些二次函数状物构成的,所以我们猜测其具有凸性,且是一个单谷函数。
于是可以通过直接三分来找到极值点,单次计算
发现复杂度的瓶颈是在于每次三分内部要要二分出是在哪一段。于是我们可以先三分出在哪一段,然后段内再三分出哪个是极值点,这样子第一次计算的时候取点我们直接取段内的左右端点即可,第二次计算的时候已经确定段了就不需要再二分段了。时间复杂度也可以做到
不过其实最后确定段之后,其中一个变量分界点
其实整数域的三分是可以用二分替代的,因为下凸函数满足差分单调递增,我们直接二分出差分值在
当然由于我的写法比较丑陋,所以还需要加入下面若干常数优化。
-
可以发现随着
w 的增大,我们的最优行走距离x 是单调递减的,所以我们可以对于询问的w 进行排序,然后每次就可以缩短二分上界了。 -
虽然是
O(1) 计算函数值,但是由于计算过程中涉及__int128,所以下面代码中的g 函数的计算会异常地慢,可以发现由于g 是段的函数值,一共只有O(n) 段,我们可以提前算出这些值,然后直接用即可。
#include<bits/stdc++.h>
#define pb emplace_back
#define fi first
#define se second
#define mp make_pair
using namespace std;
typedef long long ll;
typedef __int128 i128;
const int maxn=3e5+10;
inline int read(){
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Calc{
int c[maxn],to[maxn],p[maxn],n,m,d,W,R,lst[3];
i128 pre[maxn],s1[maxn],s2[maxn],v1[maxn],v2[maxn]; int id;
ll V(ll x){ return x*x; }
inline i128 f(ll x){ return W*x/d+pre[id]+s2[id+1]-2*x*s1[id+1]+(i128)(n-id)*x*x; }
inline i128 g(int z){
ll x1=p[z],x2=p[z+1]-d;
if(x1==x2) return W*x1/d+v1[z];
return min(W*x1/d+v1[z],W*x2/d+v2[z]);
}
inline void G(int z){
ll x1=p[z],x2=p[z+1]-d;
v1[z]=pre[to[z]]+s2[to[z]+1]-2*x1*s1[to[z]+1]+(i128)(n-to[z])*x1*x1;
v2[z]=pre[to[z]]+s2[to[z]+1]-2*x2*s1[to[z]+1]+(i128)(n-to[z])*x2*x2;
}
void init(){
sort(c+1,c+1+n); m=0; c[n+1]=2e9;
if(c[1]){ p[++m]=0; to[m]=0; }// p:坐标 to:覆盖了前缀哪些位置
for(int i=1;i<=n;i++){
int z=c[i]%d==0?c[i]:c[i]+d-c[i]%d;
if(z<c[i+1]) p[++m]=z,to[m]=i;
pre[i]=pre[i-1]+V(min(c[i]%d,d-c[i]%d));
}
p[m+1]=p[m]+d; lst[1]=lst[2]=m-1;
for(int i=n;i>=1;i--) s1[i]=s1[i+1]+c[i],s2[i]=s2[i+1]+V(c[i]);
for(int i=1;i<=m;i++) G(i);
}
ll calc(int w,int t){
if(m==1) return 0;
W=w; int l=1,r=min(lst[t],m-1);
while(l<r){
int mid=(l+r)>>1;
if(g(mid)-g(mid+1)<=0) r=mid;
else l=mid+1;
}
int id2=g(l)<g(l+1)?l:l+1; lst[t]=id2;
l=p[id2]; r=p[id2+1]-d; id=to[id2];
i128 ans=min(f(l),f(r));
if(n!=id){
i128 x1=(2*d*s1[id+1]-W)/2/d/(n-id);
ll v1=x1+d-x1%d,v2=x1-x1%d;
if(l<=v1&&v1<=r) ans=min(ans,f(v1));
if(l<=v2&&v2<=r) ans=min(ans,f(v2));
}
return (ll)ans;
}
}L,R;
int n,m,a[maxn],x0; ll ans[maxn<<1];
vector<pair<int,int> > Q;
int main(){
cin>>n;
for(int i=1;i<=n;i++) a[i]=read();
cin>>x0>>L.d; R.d=L.d;
for(int i=1;i<=n;i++)
if(a[i]<=x0) L.c[++L.n]=x0-a[i];
else R.c[++R.n]=a[i]-x0;
cin>>m; L.init(); R.init();
for(int i=1;i<=m;i++) Q.pb(read(),i);
sort(Q.begin(),Q.end());
for(auto z:Q) ans[z.se]=min(L.calc(z.fi,1)+R.calc(2*z.fi,2),L.calc(2*z.fi,2)+R.calc(z.fi,1));
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}