BZOJ1096 [ZJOI2007]仓库建设 动态规划 斜率优化
原文链接http://www.cnblogs.com/zhouzhendong/p/8696410.html
题目传送门 - BZOJ1096
题意
给定两个序列$a,b,X$,现在划分$a$序列。
被划分出来的段$[j,i]$的花费为$a_i+\sum_{k=j+1}^{i}(X_i-X_k)b_k$。
一种划分方式的花费就是每一段的花费之和。
问最小花费。
序列长度$\leq 10^6$。
题解
这题是BZOJ3437的升级版(其实也没升多少……,不仅代码我是几乎原样复制到,连题解我几乎都是原样复制的……)
首先我们不难写出DP方程:
$$dp_i=max\{dp_j+\sum_{k=j+1}^{i}(X_i-X_k)b_k\}+a_i\ \ \ \ \ (0\leq j<i)$$
然后容易看出可以斜率优化。
然后搬出斜率优化的套路。
先化简些式子。
$$dp_j+\sum_{k=j+1}^{i}(X_i-X_k)b_k\\=dp[j]-\sum_{k=j+1}^{i}X_kb_k+X_i\sum_{k=j+1}^{i}b_k$$
设:
$$sum_i=\sum_{j=1}^{i}b_j$$
$$vsum_i=\sum_{j=1}^{i}b_j\times X_j$$
则原式=
$$dp_j-vsum_i+vsum_j+X_isum_i-X_isum_j$$
然后开始斜率优化DP的套路部分:
设(注意$x$的大小写)
$$x_i=sum_i$$
$$y_i=dp_i+vsum_i$$
则原式=
$$y_j-vsum_i+X_isum_i-X_ix_j$$
假设$j>k$且从$j$转移不劣于$k$,则:
$$y_j-vsum_i+X_isum_i-X_ix_j\leq y_k-vsum_i+X_isum_i-X_ix_k$$
化简得:
$$\frac{y_j-y_k}{x_j-x_k}\leq X_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}<X_i$
我们来发掘以下$g_{j,k}$的性质。
1. 当$g_{j,k}\leq X_i$时,由于随着$i$变大,$X_i$也变大,所以显然从$k$转移是永远不会比$j$好的,所以我们可以把$k$扔掉。
2. 当$g_{i,j}\leq g_{j,k}$时,从$i$或者$k$转移至少有一个不比$j$差,所以可以把$j$扔掉。为什么??
若$g_{i,j}\leq X_i$,显然$j$要被扔掉,根据第一个性质。
若$g_{i,j}>X_i$,则$g_{j,k}>X_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],X[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%lld%lld",&X[i],&b[i],&a[i]); for (int i=1;i<=n;i++) sum[i]=sum[i-1]+b[i],vsum[i]=vsum[i-1]+b[i]*X[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])*X[i]) head++,j=q[head+1],k=q[head]; j=k; dp[i]=dp[j]+vsum[j]-vsum[i]+(sum[i]-sum[j])*X[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; }