原文链接http://www.cnblogs.com/zhouzhendong/p/8696321.html
题目传送门 - BZOJ3437
题意
给定两个序列$a,b$,现在划分$a$序列。
被划分出来的段$[j,i]$的花费为$a_i+sum_{k=j+1}^{i}(i-k)b_k$。
一种划分方式的花费就是每一段的花费之和。
问最小花费。
序列长度$leq 10^6$。
题解
首先我们不难写出DP方程:
$$dp_i=max{dp_j+sum_{k=j+1}^{i}(i-k)b_k}+a_i (0leq j<i)$$
然后容易看出可以斜率优化。
然后搬出斜率优化的套路。
先化简些式子。
$$dp_j+sum_{k=j+1}^{i}(i-k)b_k\=dp[j]-sum_{k=j+1}^{i}kb_k+isum_{k=j+1}^{i}b_k$$
设:
$$sum_i=sum_{j=1}^{i}b_j$$
$$vsum_i=sum_{j=1}^{i}b_j imes j$$
则原式=
$$dp_j-vsum_i+vsum_j+isum_i-isum_j$$
然后开始斜率优化DP的套路部分:
设
$$x_i=sum_i$$
$$y_i=dp_i+vsum_i$$
则原式=
$$y_j-vsum_i+isum_i-ix_j$$
假设$j>k$且从$j$转移不劣于$k$,则:
$$y_j-vsum_i+isum_i-ix_jleq y_k-vsum_i+isum_i-ix_k$$
化简得:
$$frac{y_j-y_k}{x_j-x_k}leq 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}<i$
我们来发掘以下$g_{j,k}$的性质。
1. 当$g_{j,k}leq i$时,由于随着$i$变大,$i$也变大,所以显然从$k$转移是永远不会比$j$好的,所以我们可以把$k$扔掉。
2. 当$g_{i,j}leq g_{j,k}$时,从$i$或者$k$转移至少有一个不比$j$差,所以可以把$j$扔掉。为什么??
若$g_{i,j}leq i$,显然$j$要被扔掉,根据第一个性质。
若$g_{i,j}>i$,则$g_{j,k}>i$,那么显然$j$比$k$差,也得被扔掉。
于是我们可以用一个单调队列来维护斜率的单调性。
具体的:
当情况1发生的时候让队首出队。
在进队的时候,如果发生情况2,那么先让队尾出队,然后再进队。
为了避免精度问题,我们可以把$x_i-x_j$乘上来。
代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N=1000005; int n,q[N],head=1,tail=0; LL a[N],b[N],sum[N],vsum[N],dp[N],x[N],y[N]; int main(){ scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%lld",&a[i]); for (int i=1;i<=n;i++){ scanf("%lld",&b[i]); sum[i]=sum[i-1]+b[i]; vsum[i]=vsum[i-1]+b[i]*i; } q[++tail]=0; for (int i=1;i<=n;i++){ int j=q[head+1],k=q[head]; while (tail-head>0&&y[j]-y[k]<=(x[j]-x[k])*i) head++,j=q[head+1],k=q[head]; j=k; dp[i]=dp[j]+vsum[j]-vsum[i]+(sum[i]-sum[j])*i+a[i]; x[i]=sum[i]; y[i]=dp[i]+vsum[i]; j=q[tail],k=q[tail-1]; while (tail-head>0&&(y[i]-y[j])*(x[j]-x[k])<=(y[j]-y[k])*(x[i]-x[j])) tail--,j=q[tail],k=q[tail-1]; q[++tail]=i; } printf("%lld",dp[n]); return 0; }