斜率优化dp

斜率优化dp是一种优化动态规划算法的方法,通过数形结合将决策点映射到坐标系,利用斜率进行优化。文章介绍了如何将n^2的dp算法转化为斜率优化形式,并详细解释了做法,包括如何将表达式转换、引入变量简化式子,以及如何应用单调队列维护最优解。难点在于归纳转化、边界条件的处理以及凹包情况的应对。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

斜率优化dp

斜率优化dp的思想是数形结合,将各种决策点反映在平面直角坐标系中,然后通过斜率进行优化

做法

首先将这道题的n2n^2n2的dp 算法写出来
然后将其暴力展开
如:f(i)=min(f(j)+(s[i]−s[j]+i−j−1−l)2)f(i)=min(f(j)+(s[i]-s[j]+i-j-1-l)^2)f(i)=min(f(j)+(s[i]s[j]+ij1l)2)
s[i]=s[i]+i,l=l+1s[i]=s[i]+i,l=l+1s[i]=s[i]+i,l=l+1
原式变为f(i)=min(f(j)+(s[i]−s[j]−l)2)f(i)=min(f(j)+(s[i]-s[j]-l)^2)f(i)=min(f(j)+(s[i]s[j]l)2)
暴力展开后是长这样的:f(i)=f(j)+s[i]2+s[j]2+l2−2s[i]s[j]−2s[i]l+2s[j]lf(i)=f(j)+s[i]^2+s[j]^2+l^2-2s[i]s[j]-2s[i]l+2s[j]lf(i)=f(j)+s[i]2+s[j]2+l22s[i]s[j]2s[i]l+2s[j]l
我们可以将这个式子的右边转化为这种形式:只和j有关 和i,j有关 只和i有关 以及常数项
f(i)=f(j)+s[j]2+2s[j]l+2s[i]s[j]+s[i]2−2s[i][l]+l2f(i)=f(j)+s[j]^2+2s[j]l+2s[i]s[j]+s[i]^2-2s[i][l]+l^2f(i)=f(j)+s[j]2+2s[j]l+2s[i]s[j]+s[i]22s[i][l]+l2
现在我们可以玄学地令:
b=f[i]b=f[i]b=f[i]
y=f(j)+s[j]2+2ls[j]y=f(j)+s[j]^2+2ls[j]y=f(j)+s[j]2+2ls[j]
k=−2s[i]k=-2s[i]k=2s[i]
x=s[j]x=s[j]x=s[j]
这样这个式子就转换为了:
b=y−k∗x+一堆常数项b=y-k*x+一堆常数项b=ykx+
因为当循环到i的时候 s[i]的值是固定的 所以s[i]也可以看做是定值
然后你会发现(常数项不用管,反正是常数嘛对不对,不会变的)
y=kx+by=kx+by=kx+b 撒花!(但不是结尾撒花)
这时候就可以用我们可爱的斜率优化了
斜率优化的注释在代码里面 因为没有图所以只能可怜的在代码里面打注释了(玩具装箱)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long 
#define y(T) (f[T]+s[T]*s[T]+2*s[T])
#define x(T) (s[T])
#define k(T) (2*s[T]-2*l)
using namespace std;
const int maxn=50000;
ll n,l;
ll cost[maxn],s[maxn],head,tail,f[maxn];
ll q[maxn];
inline double K(ll cxk,ll nmxl)
{
	return 1.0*(y(nmxl)-y(cxk))/(x(nmxl)-x(cxk));
}

signed main()
{
	scanf("%lld %lld",&n,&l);
	for(register ll i=1;i<=n;i++)
	{
		scanf("%lld",&cost[i]);
		s[i]=s[i-1]+cost[i];
	}
	for(register ll i=0;i<=n;i++)
		s[i]+=i;
	head=1;tail=1;//初始化都为1 表示严格包含head和tail区间这样可以表示已经有一个点0在队列里面了 而斜率优化dp必须在一开始的时候压一个点0进去才能保证第一段选出来
	for(register ll i=1;i<=n;i++)
	{
		while(head<tail && K(q[head],q[head+1])<k(i))	//注意此处head<tail因为必须有两个元素
			head++;//维护头,显然q[head]不是最优解(以后也不会是)弹掉
		f[i]=f[q[head]]+(s[i]-s[q[head]]-1-l)*(s[i]-s[q[head]]-1-l);
		while(head<tail && K(q[tail],i)<=K(q[tail-1],q[tail]))	
			tail--; //遇到凹包 需要将再队列中的点斜率大于新加入的点全部弹掉
		q[++tail]=i;
	}
	printf("%lld",f[n]);
	return 0;
} 

emm总而言之斜率优化的难点就在于把n2n^2n2算法的dp写出来之后要进行归纳,神奇的令,以及对队列tail的维护与遇到凹包的时候如何处理
最毒瘤的是初始化以及边界条件 head=1 tail=1 head<tail
然后就没什么了
update:每一个iii实际上都对应了一个点以及斜率。并不只是一个单纯的iii 另外我们对于这个斜率的式子,实际上我们需要找到的是一个kkk和一个(x,y)(x,y)(x,y) 使得与yyy轴的交点尽量小。 而且这个(x,y)(x,y)(x,y)kkk 是没有关系的,在单调队列中,我们存储的实际上是(x,y)(x,y)(x,y) 斜率是在forforfor循环枚举。斜率优化实际上就是维护了对于当前fff我们需要找到之前的最好决策,单调队列的对头就是我们维护的之前的最好决策,而最终的答案还是f[n]f[n]f[n]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值