学习篇 ------ 斜率优化学习

今天重新开始看斜率优化,以前学的差不多忘完了。。。

由于斜率优化是对dp的优化,所以这里我们用具体的题目来讲解斜率优化。

第一道题讲得极其详细,如果你认真看了,一定能看懂。(这个斜率优化本身就要推好长的式子,不要嫌烦QwQ)。后面题都是差不多的套路。

 

1 . BZOJ1010: [HNOI2008]玩具装箱toy

比较入门的一道题,题意是给你一列玩具,让你把它们分组,使得一个它给定的式子的和尽可能小。

还是自己先看题吧。。。https://www.lydsy.com/JudgeOnline/problem.php?id=1010

刚看到这道题时,我们先想到一个简单的dp,令f[i]表示前i个玩具所得的最小价值。

然后我们可以列出式子f[i]=min{f[j]-[(i-(j+1))+(s[i]-s[j])-L]^2}  其中j<i,s[i]为玩具价值的前缀和

直接做是n^2的,肯定过不了。

我们考虑在dp的过程中是否有多余的决策。

先把 f[i]=f[j]-[(i-(j+1))+(s[i]-s[j])-L]^2 化简一下,把到第i位为定值的放在一起。

f[i]=f[j]+[(i+s[i]-L-1)-(j+s[j])]^2

然后令 g[i]=i+s[i]-L-1 ,b[j]=j+s[j] ,则上面的式子化为 f[i]=f[j]+(g[i]-b[j])^2

展开后为 f[i]=f[j]+g[i]^2-2*g[i]*b[j]+b[j]*b[j]

假设k<j<i,并且j优于k。

i由j转移过来:f[i]=f[j]+g[i]^2-2*g[i]*b[j]+b[j]*b[j]

i由k转移过来:f[i]=f[k]+g[i]^2-2*g[i]*b[k]+b[k]*b[k]

因为假设 j转移到i 优于 k转移到i ,则 f[j]+g[i]^2-2*g[i]*b[j]+b[j]*b[j] < f[k]+g[i]^2-2*g[i]*b[k]+b[k]*b[k]

即(f[j]+b[j]^2)-(f[k]+b[k]^2)<2*g[i]*(b[j]-b[k])

因为 b[j]=j+s[j],k<j,且玩具的价值都为正数,所以b[k]<b[j]。等式右边的(b[j]-b[k])可以除到左侧,得到下式:

[(f[j]+b[j]^2)-(f[k]+b[k]^2)]/(b[j]-b[k])<2*g[i]

(这里解释一下,上面这个式子如果满足,则之前假设的 j转移到i 优于 k转移到i 成立,那么k状态可以直接删掉,因为它不是最优的。反之,如果不满足,那么就是 k转移到i 优于 j转移到i。)

左侧类似斜率的形式,右侧为一个定值。(假设当前正在计算f[i],右侧2*g[i]都是已知的)

然后就到了斜率优化的地方。

首先我们看两个结论。(图片来自 中山市中山纪念中学金牌教练宋新波老师2016年在绵阳南山中学冬令营的讲稿)

T(x,y)为斜率。

                                                                                 图1

                                                                            图2

结论的证明讲得非常详细(可惜当年我在冬令营的时候冬眠了,到现在才发现这个讲稿这么好),如果不想看证明,只记结论也行。

根据结论,我们发现实际上我们只需要维护一个下凸包,然后取队首即可。

具体步骤(用队列维护):

1. 先从队首看队首是否满足>2*g[i]( 即图2中的2*g[i]<T(j[1],j[2]) ),不是就删队首,直到满足为止。

2. 然后用队首的最优决策得到当前的最优解。

3. 从队尾开始判断如果插入当前的新决策i是否满足一个下凸包。( 即图2中的T(j[k-1],j[k])<T(j[k],i) ),不是就删队尾,直到满足为止。

4. 然后把i决策插到队尾。

然后呢。。。然后就没了,输出就行了。

讲这么清楚应该会写了吧。。。还不会写的你来评论区,咱俩坐下好好谈一下。。。

代码:

#include<bits/stdc++.h>
#define LL long long
#define eps 1e-3
#define MAXN 50010
using namespace std;
LL s[MAXN],b[MAXN],g[MAXN],f[MAXN],q[MAXN];
int dcmp(double k)
{
	if(fabs(k)<eps)return 0;
	else if(k<0)return -1;
	else return 1;
}
double XL(LL j,LL k){return (1.0*((f[j]+b[j]*b[j])-(f[k]+b[k]*b[k])))/(1.0*(b[j]-b[k]));}
int main()
{
	LL n,L,a,i,head,tail;
	scanf("%lld %lld",&n,&L);
	for(i=1;i<=n;i++){scanf("%lld",&a);s[i]=s[i-1]+a;}
	
	//b[i]=i+s[i]
	//g[i]=i+s[i]-L-1
	for(i=1;i<=n;i++)
	{
		b[i]=i+s[i];
		g[i]=i+s[i]-L-1;
	}
	
	//f[i]为前i个玩具的最小费用
	f[0]=0LL;head=0;tail=0;q[tail]=0;
	for(i=1;i<=n;i++)
	{
		while(head<tail&&XL(q[head],q[head+1])<2*g[i])head++;
		if(head<=tail)f[i]=f[q[head]]+(g[i]-b[q[head]])*(g[i]-b[q[head]]);
		while(head<tail&&XL(q[tail],i)<XL(q[tail-1],q[tail]))tail--;
		q[++tail]=i;
	}
	printf("%lld",f[n]);
	return 0;
}

2 . BZOJ1597: [Usaco2008 Mar]土地购买

首先我们发现如果一个土地比另外一个土地的长宽都大,那小的那个直接就不用了。然后我们把这些不用的一删,然后按长从小到大排序,会发现宽是从大到小排序的(自己写写看)。然后就可以dp了。

定义f[i]为i块土地的最小费用。

f[i]=f[j]+a[i]*b[j+1]

然后和第一题一样化式子,斜率优化即可。

代码:

#include<bits/stdc++.h>
#define LL long long
#define eps 1e-8
using namespace std;
struct node
{
	LL a,b;
}p[50010],cc[50010];
LL f[50010],q[50010];
bool cmp(node aa,node bb)
{
	if(aa.a==bb.a)return aa.b>bb.b;
	else return aa.a<bb.a;
}
LL dcmp(double k)
{
	if(fabs(k)<eps)return 0LL;
	else if(k<0)return -1LL;
	else return 1LL;
}
double XL(LL j,LL k)
{
	return (1.0*(f[j]-f[k]))/(1.0*(cc[k+1].b-cc[j+1].b));
}
int main()
{
	//freopen("acquire.in","r",stdin);
	//freopen("acquire.out","w",stdout);
	LL n,i,lcc,head,tail,mx;
	scanf("%lld",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%lld %lld",&p[i].a,&p[i].b);
	}
	sort(p+1,p+n+1,cmp);
	
	lcc=0;mx=0; 
	for(i=n;i>=1;i--)
	{
		if(p[i].b>mx)cc[++lcc]=p[i];
		mx=max(mx,p[i].b);
	}
	
	sort(cc+1,cc+lcc+1,cmp);
	
	//for(i=1;i<=lcc;i++)printf("%lld %lld\n",cc[i].a,cc[i].b);
	
	memset(f,0,sizeof(f));
	head=0;tail=0;q[tail]=0;
	for(i=1;i<=lcc;i++)
	{
		while(head<tail&&dcmp(XL(q[head],q[head+1])-cc[i].a)<0)head++;
		if(head<=tail)f[i]=f[q[head]]+cc[i].a*cc[q[head]+1].b;
		while(head<tail&&dcmp(XL(q[tail],i)-XL(q[tail-1],q[tail]))<0)tail--;
		q[++tail]=i;
	}
	printf("%lld",f[lcc]);
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值