u1s1 在我完成这篇题解之前,全网总共两篇题解,一篇使用的平衡树,一篇使用的就是这篇题解讲解的这个做法,但特判掉了一个点,把特判去掉在 BZOJ 上会 WA 一个点。
两篇题解都异常简略,前一篇题解甚至只有代码没有说明,所以这里我来写篇比较详细题解造福人类了(大雾
建议没做过这题的同学先看看这道题,看题解区第一个做法,对这题有比较大的启发。
注:下文中分别用 (a,b,l,r) 代替题面中的 (X,Y,A,B)。
首先关于这题咱们可以想到一个非常 naive 的 DP,(dp_{i,j}) 表示考虑到前两个数,(b_i=j) 的 (sumlimits_{k=1}^i|a_k-b_k|)。那么显然有 DP 转移 (dp_{i,j}=minlimits_{k=l}^rdp_{i-1,j-k}+|a_i-j|)。一脸不好直接维护的样子。不过注意到绝对值函数是一个下凸函数,因此我们猜测对于一个固定的 (i),(dp_{i,j}) 随 (j) 的增大也是一个下凸函数,事实也的确如此,证明大概可以归纳,可能需要一些分类讨论,这里不再赘述,留给读者自己思考。
我们还可以注意到,每一轮中每条直线斜率变化的绝对值最多为 (1),也就是说任意时刻,下凸壳中直线斜率绝对值的最大值顶多为 (n),因此我们考虑维护 (2n+2) 个分界点的横坐标,具体来说,记 (x_i) 为第 (i) 个分界点,那么以凸包上横坐标分别为 (x_i,x_{i+1}) 的点组成线段刚好是凸包上斜率为 (i-n-2) 的直线,当然也有可能 (x_i=x_{i+1}),此时凸包上不存在斜率为 (i-n-2)(说白了就是记录凸包拐点的横坐标)。
接下来考虑加入一个 (a_i) 后会对凸包产生怎样的影响。我们考虑将每一轮转移的过程分成两部分:(dp_{i,j}=minlimits_{k=l}^rdp_{i-1,j-k}) 和 (dp_{i,j}:=dp_{i,j}+|a_i-j|),首先对于第一部分而言,显然在凸包上存在一些分界点,满足分界点前面单调不增,分界点之后单调不降,不难发现这些分界点组成的集合就是斜率为 (0) 的直线。假设斜率为 (0) 的直线的两个端点横坐标为 (u,v(u,v)),那么不难发现对于 (jle u+l),由于 (u) 前面单调递减,因此我们肯定会尽量选择 (j) 与接近的点转移,即 (dp_{i,j-l}),因此我们可以得到,进行第一步操作后,凸包上 (u) 前面的点都会向右平移 (l) 格,同理在 (v+r) 后面的点 (j),我们肯定会尽量选离 (j) 远的点的即 (j-r),也就是说 (v) 后面的点肯定会向右平移 (r) 格,至于中间的部分……那显然还是一条水平的直线咯(
第二部分就相对比较简单了,不难发现 (f(x)=|a_i-x|) 在 ((-infty,a_i)) 中是斜率为 (-1) 的直线,((a_i,infty)) 中是斜率为 (1) 的直线,因此 (a_i) 前面的部分的直线的斜率会减 (1),后面的部分斜率会加 (1)。这样我们可以看作是在 (a_i) 处插入了两个断点。
考虑怎样维护这个东西,我们开两个堆 (L,R) 分别存储斜率为 (0) 的直线前面和后面的断点,那么时刻 (i) 时显然 (L) 应当为存储的是最小的 (i) 个断点,(R) 应当存储最大的 (i) 个断点,但有时并不一定真的存储的就是最小/最大的 (i) 个断点,这时候我们就要进行调整,具体方法就是取出 (L) 中最大的元素 (x) 和最小的元素 (y),如果 (x>y) 就将 (x) 插入 (R),(y) 插入 (L),否则 break
。还有一个问题就是如何处理坐标平移,具体方法就在 (i) 时刻是将 (x) 插入 (L) 时我们不插入 (x) 本身,而是插入 (x-(i-1)l),这样在 (i’) 时刻这个点真正的坐标就是 (x+i’l),不难发现这里用了差分的思想,在统计每个点有多少个时刻满足 xxxx 条件时也用到了类似的思想。
时间复杂度 (nlog n)
注意加一些特判,否则在 BZOJ 上会 WA 一个点,进而取得 (0) 分的好成绩(London Fog
const int MAXN=5e5;
int n,mx,l,r,a[MAXN+5],b[MAXN+5];ll ext=0;
priority_queue<ll> L;
priority_queue<ll,vector<ll>,greater<ll> > R;
//void prt(auto q){
// while(!q.empty()) printf("%d ",q.top()),q.pop();
// printf("
");
//}
int main(){
scanf("%d%d%d%d",&n,&mx,&l,&r);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]<1ll*(i-1)*l+1){
ext+=abs(a[i]-(1ll*(i-1)*l+1));
a[i]=1ll*(i-1)*l+1;
}
}
for(int i=1;i<=n;i++){
L.push(a[i]-1ll*(i-1)*l);
R.push(a[i]-1ll*(i-1)*r);
while(1){
ll x=L.top();x+=1ll*(i-1)*l;
ll y=R.top();y+=1ll*(i-1)*r;
// printf("%lld %lld
",x,y);
if(x<=y) break;L.pop();R.pop();
L.push(y-1ll*(i-1)*l);R.push(x-1ll*(i-1)*r);
} b[i]=L.top()+1ll*(i-1)*l;
// prt(L);prt(R);
} ll res=0;chkmin(b[n],mx);
chkmax(b[n],1ll*l*(n-1)+1);//be careful
for(int i=n-1;i;i--){
if(b[i]<b[i+1]-r) b[i]=b[i+1]-r;
if(b[i]>b[i+1]-l) b[i]=b[i+1]-l;
}
// if(n==8888) for(int i=1;i<=n;i++) printf("%d%c",b[i],"
"[i==n]);
for(int i=1;i<=n;i++) res+=abs(a[i]-b[i]);
printf("%lld
",res+ext);
return 0;
}