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;
}

  

posted @ 2018-04-02 19:14  zzd233  阅读(300)  评论(0编辑  收藏  举报