斜率优化 笔记

说道斜率优化,我就想起今年下半年 湖南2008年的一个题。相信大家也不陌生(如果您是第一次学斜率优化,那估计挺陌生的)。题目叫:HNOI2008 玩具装箱TOY。题意大概是这个样子:

题意简述

给你一个序列a[1,n]a_{[1,n]}a[1,n]和一个常数kkk。请你把这个序列分成若干段。定义:一个段[l,r][l,r][l,r]需要占用的空间XXX就等于r−l+∑i=lrair-l+\sum\limits_{i=l}^{r} a_irl+i=lrai,然后需要的花费就是(X−k)2(X-k)^2(Xk)2。其中这个XXX没有限制,珂以无限长(只不过不是最优就是了)。请求出所有段的花费的最小值。

暴力思路

如果您来看斜率优化的话,相信您也不难退出这个题的暴力dpdpdp。设dp[i]dp[i]dp[i]表示前iii个数分成一些段使得总花费最小。那么,dp[i]=mindp[j]+cost(j+1,i)dp[i]=min{dp[j]+cost(j+1,i)}dp[i]=mindp[j]+cost(j+1,i)。其中cost(j+1,i)cost(j+1,i)cost(j+1,i)表示从j+1j+1j+1iii这些数在一段的花费。为了稍微快一点,设sumsumsum表示aaa的前缀和。那么cost(j+1,i)cost(j+1,i)cost(j+1,i)就等于[(i−j−1+sum[i]−sum[j])−l]2[(i-j-1+sum[i]-sum[j])-l]^2[(ij1+sum[i]sum[j])l]2

但是这个dpdpdpO(n2)O(n^2)O(n2)的。能不能再快一点呢?(严格来讲,你必须要再快一点,要不然这个时间就卡不过去)

正片开始:斜率优化

把关于iii的项和关于jjj的项单独提出来。变成:

a(x)=x+sum[x],b(x)=x+sum[x]+l+1a(x)=x+sum[x],b(x)=x+sum[x]+l+1a(x)=x+sum[x],b(x)=x+sum[x]+l+1。那么原式等于(a(i)−b(j))2(a(i)-b(j))^2(a(i)b(j))2

稍稍改变jjj的定义,令jjj是满足条件中最优的jjj

那么:(推式子警告)

dp[i]=dp[j]+(a(i)−b(j))2dp[i]=dp[j]+a(i)2−2a(i)b(j)+b(j)2dp[i]+2a(i)b(j)−a(i)2=dp[j]+b(j)2 dp[i]=dp[j]+(a(i)-b(j))^2\\ dp[i]=dp[j]+a(i)^2-2a(i)b(j)+b(j)^2\\ dp[i]+2a(i)b(j)-a(i)^2=dp[j]+b(j)^2 dp[i]=dp[j]+(a(i)b(j))2dp[i]=dp[j]+a(i)22a(i)b(j)+b(j)2dp[i]+2a(i)b(j)a(i)2=dp[j]+b(j)2

把这个式子放到平面直角坐标系上,以b(j)b(j)b(j)xxxdp[j]+b(j)2dp[j]+b(j)^2dp[j]+b(j)2yyy

对于每个jjj来说,点(b(j),dp[j]+b(j)2)(b(j),dp[j]+b(j)^2)(b(j),dp[j]+b(j)2)就是一个决策点了。

而且,显然这是一个直线的关系,它的斜率就是2a(i)2a(i)2a(i),截距是dp[i]−a(i)2dp[i]-a(i)^2dp[i]a(i)2。我们把直线向上平移a(i)2a(i)^2a(i)2,就是最小的dp[i]dp[i]dp[i]。由于a(i)2a(i)^2a(i)2的值是确定的(我们在找jjj的过程中,iii是不会变的),所以在同一个iii里面,我们相当于要让截距最小。

这样,对于每个iii(注意,和上面不一样,不在同一个iii里面了),珂能作为最优点选择出来的点应该组成一个下凸包(因为a(i)a(i)a(i)是单调递增的,所以斜率单调递增,即组成下凸包)。

a(i)a(i)a(i)的单调递增性我就不讲了)

为啥?考虑这种情况:

AAABBBCCC是珂供选择的三个决策点)

当前的话,如果AAA还没滚蛋,应该是AAABBB优的。如图

随着斜率的增加,也许会出现CCCAAABBB都优的情况。如图:

blog3.jpg

容易证明,CCC此时一枝独秀,AAABBB都珂以滚蛋了。这两种情况有一个共同点,就是BBB都不能再优了。anyway,滚蛋吧。

还有一种情况(肯定还有,只有刚刚那一个是没法保证正确性的),就是最前面两个元素还要满足一个条件:第一个点和第二个点之间的斜率要>=2a(i)>=2a(i)>=2a(i),否则第一个就再也不珂能作为最优解出现了,因为此时随着斜率的单调递增,后面的斜率肯定会更大,在当下的2a(i)2a(i)2a(i)的斜率中第二个都比第一个优,后面的话就不知道优到哪里去了。所以第一个就再起不能,滚了。

如图:

blog4.jpg

还是刚刚那张图,不过是另一种滚蛋方法。此时红色直线的斜率=2a(i)2a(i)2a(i)=202020。当红色直线斜率为202020的时候,AAA就没有BBB优了。如果再高一点,显然,还是没有BBB优。所以以后这个AAA都不珂能比BBB更优了。AAA就珂以滚蛋了。

说道这里,我们发现,这个东西珂以用一个单调队列维护。维护完一个正确的队列之后,Q[head]Q[head]Q[head]就是最优解。

设队列为QQQ,首尾分别叫head,tailhead,tailhead,tail

总结一下,就是要维护两个关系:

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
	#define N 112345
	#define int long long 

	#define F(i,l,r) for(int i=l;i<=r;++i)
	#define D(i,r,l) for(int i=r;i>=l;--i)
	#define Fs(i,l,r,c) for(int i=l;i<=r;c)
	#define Ds(i,r,l,c) for(int i=r;i>=l;c)
	#define Tra(i,u) for(int i=G.Start(u);~i;i=G.Next(i))
	#define MEM(x,a) memset(x,a,sizeof(x))
	#define FK(x) MEM(x,0)
	int n,l;
	int c[N];
	void R1(int &x)
	{
	    x=0;char c=getchar();int f=1;
	    while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
	    while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	    x=(f==1)?x:-x;
	}
	void Input()
	{
		R1(n),R1(l);
		F(i,1,n) R1(c[i]),c[i]+=c[i-1];
	}

	int Q[N];
	int dp[N];
	int a(int i){return c[i]+i;}
	int b(int i){return c[i]+i+l+1;}
	int x(int i){return b(i);}
	int y(int i){return dp[i]+b(i)*b(i);}
	int slope(int i,int j)//计算斜率
	{
		return (y(i)-y(j))/(x(i)-x(j));
	}
	void Soviet()
	{
		int head=1,tail=1;
		F(i,1,n)
		{
			while(head<tail and slope(Q[head],Q[head+1])<2*a(i)) ++head;
                        //垃圾决策点出队列
			dp[i]=dp[Q[head]]+(a(i)-b(Q[head]))* (a(i)-b(Q[head]));
			//Q[head]就是最优的决策点。
			while(head<tail and slope(i,Q[tail-1])<slope(Q[tail-1],Q[tail])) --tail;
			Q[++tail]=i;
                        //把i加入队列
		}
		printf("%lld\n",dp[n]);
	}
	void IsMyWife()
	{
		Input();
		Soviet();
	}
	#undef int //long long 
}
int main()
{
	Flandre_Scarlet::IsMyWife();
	getchar();getchar();
	return 0;
}

还有一些答疑时间(根据作者自身出现的理解问题编写,以后支持评论功能之后珂能会根据评论区中的问题编写。)

Q:既然Q[head]Q[head]Q[head]是最优的,为啥还要存着后面的点呢?
A:后面的点虽然现在不是最优的,但是它们很有潜力,等以后斜率上去之后,也许Q[head]Q[head]Q[head]就滚蛋了,后面那些点就变成了最优。注意上面,我们说的是珂能成为最优的点,而不是现在最优的点。要为未来着想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值