YBTOJ&洛谷P3195:玩具装箱(斜率优化dp)

传送门

文章目录

前言

斜率优化dp,就是利用斜率优化的dp

(逃)

解析

第一道斜优的题
分析题目
s u m i sum_i sumi为1-i的c的前缀和
容易写出dp转移式:
d p i = m i n ( d p j + ( s u m i − s u m j + i − j − 1 − L ) 2 ) dp_i=min(dp_j+(sum_i-sum_j+i-j-1-L)^2) dpi=min(dpj+(sumisumj+ij1L)2)

但是平方转移会T掉
考虑优化
设:
a i = s u m i + i a_i=sum_i+i ai=sumi+i
b i = s u m i + i + 1 + L b_i=sum_i+i+1+L bi=sumi+i+1+L
注意到对于固定的 i,a和b都是可求的定值
那么上面的dp,就可以写成:
d p i = m i n ( d p j + ( a i − b j ) 2 ) dp_i=min(dp_j+(a_i-b_j)^2) dpi=min(dpj+(aibj)2)
把平方拆开:
d p i = m i n ( d p j + a i 2 + b j 2 − 2 ∗ a i ∗ b j ) dp_i=min(dp_j+a_i^2+b_j^2-2*a_i*b_j) dpi=min(dpj+ai2+bj22aibj)
为了转移优化,我们需要移一下项:
2 ∗ a i ∗ b j + d p i − a i 2 = d p j + b j 2 2*a_i*b_j+dp_i-a_i^2=dp_j+b_j^2 2aibj+dpiai2=dpj+bj2

上面那个式子可以看成一个 b j b_j bj为未知数,斜率为 2 ∗ a i 2*a_i 2ai,且经过 ( b j , d p j + b j 2 ) (b_j,dp_j+b_j^2) (bj,dpj+bj2)的一次函数
没明白?这么看:
f ( x ) = 2 ∗ a i ∗ x + d p i − a i 2 f(x)=2*a_i*x+dp_i-a_i^2 f(x)=2aix+dpiai2
f ( b j ) = d p j + b j 2 f(b_j)=dp_j+b_j^2 f(bj)=dpj+bj2
d p i dp_i dpi就是这个函数与y轴的截距加上 a i a_i ai的平方,因此我们实际上就是要使函数在y轴上的截距最小
有了这个函数之后,我们就可以开始尝试优化了

对于每一个新的要求的dp[i],其直线对应的斜率是固定的
我们维护一个可以作为转移点的队列,其中的决策点对应的点对(b[x],dp[x]+b[x]^2)形成一个凸包的结构
在这里插入图片描述
由于随着 i 的增大,其直线的斜率( 2 ∗ a i 2*a_i 2ai)单调递增,所以位于凸包上方的点是一定不会作为最优决策点的
若记A、B两点之间的斜率为slope(A、B)
不难看出,要使其这条直线的y轴截距最小,我们应该找到**第一个
s l o p e ( P [ j ] , P [ j + 1 ] ) > 2 ∗ a   i   slope(P[j],P[j+1])>2*a~i~ slopeP[j],P[j+1])>2a i  的位置
而且由于斜率单调递增,Pj左侧的点以后一定不会再被选到了
所以我们可以用一个单调队列来维护
时间复杂度降为O(n)

代码

#include<bits/stdc++.h>
#define I register int
using namespace std;
#define ll long long
const int N=5e4+10;
ll read(){
	ll x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int n;
ll sum[N],c[N],a[N],b[N],l,dp[N];
struct pos{
	ll x,y;
	int pl;
};
pos q[N];
int st,ed;
double slope(pos u,pos v){
	return 1.0*(v.y-u.y)/(v.x-u.x);
}
int main(){
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
	n=read();l=read();
	for(int i=1;i<=n;i++){
		//printf("ok i=%d\n",i);
		c[i]=read();
		sum[i]=sum[i-1]+c[i];
		a[i]=sum[i]+i;b[i]=sum[i]+i+l+1;
	}
	b[0]=l+1;
	st=ed=1;q[1]={b[0],b[0]*b[0],0};
	for(int i=1;i<=n;i++){
		ll k=2*a[i];
		while(st<ed&&slope(q[st],q[st+1])<k) st++;
		ll x=q[st].x,y=q[st].y;int pl=q[st].pl;
		dp[i]=y+a[i]*a[i]-2*a[i]*b[pl];
		//printf("i=%d st=%lld dp=%lld k=%lld\n  ",i,q[st].pl,dp[i],k);
		//for(int i=st;i<=ed;i++) printf("%d:(%lld %lld %lld)  ",i,q[i].x,q[i].y,q[i].pl);
		//printf("\n\n");
		pos now=(pos){b[i],dp[i]+b[i]*b[i],i};
		while(st<ed&&slope(q[ed-1],q[ed])>slope(q[ed-1],now)){
			ed--;
		//	printf("ou2!\n");
		}
		q[++ed]=now;
	}
	printf("%lld",(long long)dp[n]);
	return 0;
}
/*
6 10
5
8
5
10
19
1
*/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值