[动态规划] 斜率优化

[动态规划] 斜率优化

$\ $

与单调队列优化的区别

前面讲过,单调队列优化的是状态变量和决策变量分开的动态规划(在转移式中可以进行分离)

比如一些类似于:

\(ans=max\{S[i]-min_{i-M\leq j\leq i-1}\{S[j]\} \}\)

\(F[i,j]=max_{j-L_i\leq k\leq S_i-1}\{F[i-1,k]+P_i*(j-k)\}\)

这都是状态和决策可以分离的,对于每一个阶段维护一个单调队列,还有下面的多重背包:

\(F[u+p*V_i]=max_{p-C_i\leq k\leq p-1}\{F[u+k*V_i]+(p-k)*W_i\}\)

详见:单调队列优化DP

总结一下,无妨就是 多项式 \(val(i,j)\) 的每一项仅与一个未知量有关

斜率优化的特点

  • 斜率优化用于解决 \(val(i,j)\) 包含 \(i,j\) 乘积项的问题

  • 结合计算几何知识得到最小值或最大值

  • 最小值对应下凸包,最大值对应上凸包,因题而定

其思路来自于一个叫线性规划的东西:

\(z=-2x+y\) 转化成 \(y=kx+b\) 的形式

典型例题

P3195 [HNOI2008]玩具装箱

不管什么优化,先写出递推式才是最重要的

\(f[i]=min\{f[j]+(sum[i]-sum[j]+i-j-L-1)^2\}\)

其中 \(j\in(0,i)\),并没有什么限制

对式子转化成 \(y=kx+b\) 的形式

  • \(f[i]=min\{f[j]+(sum[i]+i-(sum[j]+j+L+1))^2\}\)

  • \(A[i]=sum[i]+i,B[j]=sum[j]+j+L+1\)

  • 我们希望把 \(i\)\(j\) 打包,让求得的 \(f[i]\) 变成截距

  • \(f[j]+B[j]^2=2*A[i]*B[j]+f[i]-A[i]^2\)把i和j一定要分离开

然后 \(f[i]-A[i]^2\) 变成了截距 \(b\)\(B[j]\) 看成 \(x\)\(f[j]+B[j]^2\) 看成 \(y\)

\(y=(2*A[i])x+b\) ,其中 \(x,y\) 都是只与 \(j\) 有关的不变量,这就在坐标系内确定了一个点。

对答案产生贡献的点一定在一个下凸包上

所以我们用单调队列通过斜率维护下凸包即可(斜率和 \(x\) 具有单调性),可以用滑动窗口解决。

显然凸包上的斜率是单调递增的。

当循环到当前点时,及时排除不合理决策并维护下凸包,把当前点放入凸包内。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 5e4 + 10;
#define LL long long
double sum[maxn],f[maxn];
int n,L;
int head=1,tail=0,q[maxn];
inline double A(int i){return sum[i]+i;}
inline double B(int i){return A(i)+L+1;}
inline double X(int i){return B(i);}
inline double Y(int i){return f[i]+B(i)*B(i);}
inline double k(int i,int j){
	return (double)(Y(j)-Y(i))/(X(j)-X(i));
}
int main(){
	scanf("%d%d",&n,&L);
	for(int i=1;i<=n;i++){
		scanf("%lf",&sum[i]);sum[i]+=sum[i-1];
	}
	q[++tail]=0;//把原点放进去,否则会挂 
	for(int i=1;i<=n;i++){
		while(head<tail && k(q[head],q[head+1])<2*A(i))head++;
		f[i]=f[q[head]]+(A(i)-B(q[head]))*(A(i)-B(q[head]));
		while(head<tail && k(q[tail-1],i)<k(q[tail-1],q[tail]))tail--;
		q[++tail]=i;
		//printf("head: %d tail: %d\n",head,tail);
	}
	//for(int i=1;i<=n;i++)printf("f[%d]: %.2lf\n",i,f[i]);
	printf("%lld\n",(LL)f[n]);
	return 0;
}

一定要把原点放在队列里

单调队列有时为了避免非空,需要把 \(head\leq tail\) 改成 \(head<tail\)

因题而定


posted @ 2021-08-12 17:18  ¶凉笙  阅读(33)  评论(0编辑  收藏  举报