BZOJ3675 [Apio2014]序列分割 动态规划 斜率优化

时间:2024-01-20 23:17:33

原文链接http://www.cnblogs.com/zhouzhendong/p/8697258.html

题目传送门 - BZOJ3675

题意

  对于一个非负整数序列,小H需要重复k次以下的步骤:

  1.选择一个长度超过1的序列

  2.从任意位置将序列分割成两个非空的新序列。

  每次,小H将会得到分数。分数为两个新序列中元素和的乘积。请选择一种最佳的分割方式,使得k轮之后,使总得分最大。输出总得分。

  $n\leq 10^5,k\leq min(n-1,200)$

题解

  真是一道不错的题目。

  首先,我们要发掘一个性质。

对于一个最终的划分方案,以各种不同顺序划分,所得到的得分总是相同。

  设两个区间和做乘积并累加到答案里的过程为这两个区间对答案做了一次贡献。

  那么我们要证明划分完毕之后,任意两个连续区间都互相做了且仅互相做了一次贡献。

  考虑每次划分大区间的时候,划分出来的左边和右边每一个区间都做了贡献,而显然之后在左边的就不会和右边做贡献了,于是任意两个连续区间最多做一次贡献。

  考虑到对于子区间,我们不断进行子区间的左右子区间互相贡献,直到划分为1为止。

  可以感性理解一下,每一个最终区间一定会和其他所有最终区间贡献。

  或者也可以用一个更简易的证明:

  |s1|s2|s3|

  考虑以上的3段区间,区间和分别为s1,s2,s3,你可以自己试着先割s1和s2以及先割s2和s3,然后你会发现最后算出来的结果是相同的。

  所以我们可以从左到右分割。

  于是我们可以写出DP方程。

  $dp_{r,i}$表示分割了$r$次,分割到了$i$这个位置。

  设$sum_i=\sum_{j=1}^i a_j$。

  $$dp_{r,i}=max\{dp_{r-1,j}+sum_j(sum_i-sum_j)\}\ \ \ (0\leq j<i)$$

  显然可以斜率优化。

  稍微推导一下:

  $$dp_{r-1,j}+sum_j(sum_i-sum_j)\\=dp_{r-1,j}-sum_j^2+sum_jsum_i$$

  令

  $$x_i=sum_i$$

  $$y_i=dp_{r-1,i}-sum_i^2$$

  则原式=

  $$y_j+sum_ix_j$$

  假设$j>k$且从$j$转移不劣于$k$,则:

  $$y_j+sum_ix_jy_k+sum_ix_k$$

  化简得:

  $$\frac{y_j-y_k}{x_j-x_k}\geq -sum_i$$

  然后献上又一波斜率优化DP套路:

  注意由于开始限制了$j>k$所以$x_j-x_k>0$,所以最后两边同时相除不等式仍然成立。

  设

  $$g_{i,j}=\frac{y_i-y_j}{x_i-x_j}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (i>j)$$

  则上式可以表示为$g_{j,k}\geq -sum_i$

  我们来发掘以下$g_{j,k}$的性质。

  1. 当$g_{j,k}\geq -sum_i$时,由于随着$i$变大,$-sum_i$变小,所以显然从$k$转移是永远不会比$j$好的,所以我们可以把$k$扔掉。

  2. 当$g_{i,j}\geq g_{j,k}$时,从$i$或者$k$转移至少有一个不比$j$差,所以可以把$j$扔掉。为什么??

    若$g_{i,j}\geq -sum_i$,显然$j$要被扔掉,根据第一个性质。

    若$g_{i,j}<-sum_i$,则$g_{j,k}<-sum_i$,那么显然$j$比$k$差,也得被扔掉。

  于是我们可以用一个单调队列来维护斜率的单调性。

  具体的:

  当情况1发生的时候让队首出队。

  在进队的时候,如果发生情况2,那么先让队尾出队,然后再进队。

  为了避免精度问题,以及分母为0的问题,我们可以把$x_i-x_j$乘上来,用乘积式来判断大小。

  但是本题空间限制较为紧。

  所以要滚动。

  注意初始化还没有进行任何一次分割时候的$x_i,y_i$,我一开始还以为都是0,调了很久。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=100005;
int n,R,q[N],head,tail;
LL sum[N],x[2][N],y[2][N],dp[2][N];
int main(){
scanf("%d%d",&n,&R);
for (int i=1;i<=n;i++)
scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
int T0=1,T1=0;
for (int i=1;i<=n;i++)
x[T1][i]=sum[i],y[T1][i]=-sum[i]*sum[i];
for (int r=1;r<=R;r++){
T0^=1,T1^=1;
head=1,tail=0;
q[++tail]=0;
for (int i=1;i<=n;i++){
int j=q[head+1],k=q[head];
while (tail-head>0&&y[T0][j]-y[T0][k]>=(x[T0][j]-x[T0][k])*(-sum[i]))
head++,j=q[head+1],k=q[head];
j=k;
dp[T1][i]=dp[T0][j]+sum[j]*(sum[i]-sum[j]);
x[T1][i]=sum[i];
y[T1][i]=dp[T1][i]-sum[i]*sum[i];
j=q[tail],k=q[tail-1];
while (tail-head>0&&(y[T0][i]-y[T0][j])*(x[T0][j]-x[T0][k])>=(y[T0][j]-y[T0][k])*(x[T0][i]-x[T0][j]))
tail--,j=q[tail],k=q[tail-1];
q[++tail]=i;
}
}
printf("%lld",dp[T1][n]);
return 0;
}