学习笔记——斜率优化 dp

引入#

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

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

dpi=max{dpj+aj}

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

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

dpi=max{dpj+aj×ai}

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

流程#

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

我们很容易得到原始的 dp 式子,ci 是前缀和:

dpi=dpj+(ci+icjj1L)2

为了简化,我们设 ai=ci+ibi=ci+i+1+L

则可以转化为:

dpi=dpj+ai2+bj22aibj

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

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

2aibj+dpiai2=dpj+bj2

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

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

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

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

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

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

此时得到的 dpi 是最小的。

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

然后我们发现,斜率 2ai 是单调递增的,那么一个点一旦不满足,那么对于之后的直线也一定不会选它了,我们可以用单调队列来维护斜率。这样,我们也可以用类似于单调队列的方式来维护斜率,实现 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 @   ZCETHAN  阅读(66)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示
主题色彩