Loading

学习笔记——斜率优化 dp

引入

斜率优化,是单调队列优化的一个进阶版本,为了更好地理解,先来回顾一下单调队列吧~

所谓单调队列优化,就是对于形如:

\[dp_i=\max\{dp_j+a_j\} \]

\(dp\) 式,我们把所有的 \(dp_j+a_j\) 放进单调队列里,实现 \(O(1)\) 的转移。

这个时候,只有在式子中,每一项只有关于 \(j\) 或者只有关于 \(i\) 的。但是如果有这样一个式子:

\[dp_i=\max\{dp_j+a_j\times a_i\} \]

这时候,我们就不能只考虑 \(a_j\),因为对于每个 \(i\),其大小都是不同的。

流程

我们先来看一道例题:[HNOI2008]玩具装箱

我们很容易得到原始的 \(dp\) 式子,\(c_i\) 是前缀和:

\[dp_i=dp_j+(c_i+i-c_j-j-1-L)^2 \]

为了简化,我们设 \(a_i=c_i+i\)\(b_i=c_i+i+1+L\)

则可以转化为:

\[dp_i=dp_j+a_i^2+b_j^2-2a_ib_j \]

我们可以发现其中有一项 \(2a_ib_j\),不能用单调队列。

这时候,我先把式子移项成:

\[2a_ib_j+dp_i-a_i^2=dp_j+b_j^2 \]

其中 \(j\) 项都是已知的,我们需要从中找出使得 \(dp_i\) 最小的。

不妨,我们把 \(b_j\) 看成 \(x\)\(dp_j+b_j^2\) 看成 \(y\)。那么这个式子可以看成是一条直线,而其斜率是固定的 \(2a_i\),截距就是 \(dp_i-a_i^2\)。并且,这条直线必然经过点 \((b_j,dp_j+b_j^2)\)。这时候,我们回来考虑我们的目的:求使得 \(dp_i\) 最小。这也就是说要使截距最小,而斜率是确定的,我们可以看成是一条直线可以上下平移。

如果我们把之前已经求出的化成一个个点,其横坐标是 \(b_j\),其纵坐标是 \(dp_j+b_j^2\),此时只要这条直线经过了这个点,那么也就是说当前的截距加上 \(a_i^2\) 就是从当前的 \(j\) 转移所得到的 \(dp_i\)。为了使得 \(dp_i\) 最小,我们找到从下往上第一个经过这条直线的点转移即可。

有点绕,我们来看几张图:

假如说我们把之前已经得到的数据映射成点是酱紫的:

然后我们用当前的直线从下往上移,当碰到第一个点时就停下转移:

此时得到的 \(dp_i\) 是最小的。

所以我们只要维护一个向下的凸包就可以了:

然后我们发现,斜率 \(2a_i\) 是单调递增的,那么一个点一旦不满足,那么对于之后的直线也一定不会选它了,我们可以用单调队列来维护斜率。这样,我们也可以用类似于单调队列的方式来维护斜率,实现 \(O(1)\) 转移。

接下来,我分享我的套路:

  1. 写出原始的 \(dp\) 式子;
  2. 然后把只含有 \(j\) 项的放在右边,其他都移到左边;
  3. 接下来把既含有 \(i\) 又含有 \(j\) 的项中的 \(j\) 项看成 \(x\),右边看成 \(y\)
  4. 然后观察斜率是否满足单调性,若满足,则可以用单调队列,否则用二分;
  5. 最后考虑维护上凸还是下凸。

好了。

Code

#include<bits/stdc++.h>
#define inf 1<<30
#define INF 1ll<<60
#define ll long long
using namespace std;
const int MAXN=5e5+10;
ll a[MAXN],b[MAXN],c[MAXN],dp[MAXN];
int q[MAXN];
ll X(int i){return 2*b[i];}
ll Y(int i){return dp[i]+b[i]*b[i];}
int main()
{
	int n,L;
	scanf("%d%d",&n,&L);
	for(int i=1;i<=n;i++){
		scanf("%lld",&c[i]);
		c[i]+=c[i-1];
		a[i]=c[i]+i;b[i]=c[i]+i+1+L;
	}
	int head=0,tail=0;
	b[0]=L+1;
	for(int i=1;i<=n;i++){
		while(head<tail&&a[i]*(X(q[head+1])-X(q[head]))>(Y(q[head+1])-Y(q[head])))
			head++;
		int j=q[head];
		dp[i]=dp[j]+(a[i]-b[j])*(a[i]-b[j]);
		while(head<tail&&(Y(i)-Y(q[tail]))*(X(q[tail])-X(q[tail-1]))<(Y(q[tail])-Y(q[tail-1]))*(X(i)-X(q[tail])))
			tail--;
		q[++tail]=i;
	}
	printf("%lld\n",dp[n]);
}
/*ZMatrisutedX
dp[i]=dp[j]+(c[i]+i-c[j]-j-L)^2
a[i]->c[i]+i	b[i]->c[i]+i+L
2*b[j]*a[i]+dp[i]-a[i]^2=dp[j]+b[j]^2

*/
posted @ 2021-05-18 10:52  ZCETHAN  阅读(62)  评论(0编辑  收藏  举报