动态规划之斜率优化

其实斜率优化也是用的单调队列来的。
一般的例如: dp[ i ]=min( dp[ j ] + f(j)) ( i-k< j < i ) 我们可以用单调队列来优化,因为变量只有j。 而如dp[ i ]=min(dp[ j ]+ f ( i ) * f ( j ) )的话,i和j在一起的了,就不能单调队列优化了。
先上一道斜率优化模板题https://www.luogu.com.cn/problem/P3195
从题目我们可以得知是dp题,并且方程如下:
dp[i]=min(dp[j]+((sum[i]-sum[j])+(i-(j+1))-L)^2);
dp[i]表示前i个所需的费用,sum[i]表示前i个的前缀和。
sum[i]+i=f(i) ,sum[j]+j+L+1=f(j)
dp[i]=dp[j]+(f(i)-f(j))^2;
dp[j]+f(j)^2 = 2f(i)f(j) + dp[i]- f(i)^2;
因为我们枚举的是j,所以j是变量,而i是知道的,所以我们令dp[j]+f(j)^2 =y,f(j)=x,2
f(i)=k,dp[i]-f(i)^2=b;
上面就可以变为y=kx+b;其实求的dp[i]就是求的截距,而我们要求的最小值,就是截距最小。
我们要的是前面的某个值最小,所以我们设k<j<i, j比k优,那么因为是最小值,所以
dp[j]+f(i)^2 -2
f(i)f(j)+ f(j)^2< =dp[k]+f(i)^2- 2f(i)f(k)+f(k)^2
dp[j]+f(j)^2 -dp[k]-f(k)^2 <=2
f(i) * (f(j)-f(k))
dp[j]+f(j)^2 -dp[k]-f(k)^2/(f(j)-f(k))<=2*f(i)

所以我们令dp[i]+f(i)^2=Y(i),所以斜率Y(j)-Y(k) / ( f (j) - f (k) ) <= 2f(i)
因此如果j>k,并且斜率满足上面的不等式的话,那么j就比k优。
然后接下来要怎样呢,单调队列要何用。
我们假设k<j<i的话,斜率slope(i,j)表示i比j优,我们设slope(i,j)<slope(j,k)的话,如下图所示(上凸包)
在这里插入图片描述
有三种情况:
slope(i,j)<slope(j,k)<2
f(i)的话,i比j优,j比k优,所以j不是最优的
slope(i,j)<2f(i)<slope(j,k)的话, i比j优, k比j优(因为大于2f(i)了,所以和之前矛盾,变成k比j优),j不是最优的
2*f(i)<slope(i,j)<slope(j,k)的话,j比i优,k比j优,j不是最优的。
所以无论是哪种情况,j都不是最优的。
也就是说通过这种的话,我们无法去这个区间里找到最优的解。
因此要slope(i,j)>slope(j,k),如下图所示
在这里插入图片描述

其实我们上面说了求截距最小值嘛,所以像上面这图一样(下凸包),在j点才能取得最小值,我们可以想象一下k=2*f(i) (k>0)的直线从最下面开始往上升,最先碰到的就是j点,也就是在j点取得最小值。也就是说在j前面的。如下图
在这里插入图片描述

因为斜率是递增的( 2*f(i) 递增 ),所以前面的点都用不到,可以出队列(队头处理),那么队尾呢,我们就是tail-1< tail < i 比较,如果slope(tail-1,tail)>slope(tail,i),那么斜率就不是递增的了,所以这样的话要出队尾。

下面是题目的代码

#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdio>
#define ll long long
using namespace std;
ll dp[50010],sum[50010],q[50010];
int l;
double fi(int i){
	return sum[i]+i;
}
double fj(int j){
	return sum[j]+j+l+1;
}
double slope(int j,int k){  //斜率
	double y=dp[j]+fj(j)*fj(j)-dp[k]-fj(k)*fj(k);
	double x=fj(j)-fj(k);
	return y/x;
}
int main()
{
	std::ios::sync_with_stdio(false);
	int n;
	while(cin>>n>>l){
		for(int i=1;i<=n;i++){
			cin>>sum[i];
			sum[i]+=sum[i-1];
		}
		int head=1,tail=1;    //tail=1因为下面的条件是head<tail
		for(int i=1;i<=n;i++){
			//head<tail 因为要有两个元素
			while(head<tail&&slope(q[head],q[head+1])<=2*fi(i))  head++;   //队头操作
			int j=q[head];
			dp[i]=dp[j]+(fi(i)-fj(j))*(fi(i)-fj(j));  //转移
			while(head<tail&&slope(q[tail-1],q[tail])>=slope(i,q[tail-1])) tail--;		//队尾操作
			q[++tail]=i;
			cout<<dp[i]<<endl;
		}
		printf("%lld\n",dp[n]);
	}
}

最后总结:斜率优化就是因为i和j在一起不能只用单调队列来优化,具体看要求什么值,再去看斜率怎么样。
总结下写法,就是求斜率,就是将dp[i]=f(j)这样的,写出f(j)<f(k),那么通过移项,相消,最后求得slope(j,k)<=a[i](关于i的常量)。

最后上一道题https://atcoder.jp/contests/dp/tasks/dp_z
斜率优化也不能只套模板,特别的边界,初始化的时候特别处理
也是斜率优化的dp,代码如下:

#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define  ll long long
#define size 200020
using namespace std;
ll a[size],q[size];
ll dp[size];
double slope(int j,int k) {
	double y=dp[j]+a[j]*a[j]-dp[k]-a[k]*a[k];
	double x=a[j]-a[k];
	return y/x;
}
int main()
{
	int n;
	ll c;
	while(cin>>n>>c){
		for(int i=1;i<=n;i++)
			cin>>a[i];
		dp[0]=0;
		int head=1,tail=1;
		q[head]=1;  	//这里注意初始化,因为第一块是不消耗的
		for(int i=2;i<=n;i++){
			while(head<tail&&slope(q[head],q[head+1])<=2*a[i]) head++;
			int j=q[head];
			dp[i]=dp[j]+c+(a[i]-a[j])*(a[i]-a[j]);
			while(head<tail&&slope(q[tail-1],q[tail])>=slope(q[tail],i)) tail--;
			q[++tail]=i;
		}
		cout<<dp[n]<<endl;
	}
}



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值