斜率优化总结
首先,推荐一个大佬的博客,讲解非常详细,所以不会斜率优化的请移步这里,本博客主要讲题目分析qwq
https://www.cnblogs.com/yangsongyi/p/9630227.html
主要讲解题目:
1.[HNOI2008玩具装箱]
2.[ZJOI2007仓库建设]
3.[USACO08MAR土地征用Land Acquisition]
4.[APIO2010特别行动队]
下面,开始吧。
1.[HNOI2008玩具装箱]
题目链接:https://www.luogu.org/problemnew/show/P3195
题目分析:
/* 如何看出它是个斜率优化的题呢?
first:我们可以轻松的看出它是个DP
next:数据范围对于 $O(n^2)$显然不大友好
then: 怎么办呢? 肯定是得优化的,但怎么优化,用什么优化才是关键。*/
估计很多读者(呃。。such as本人)想到这里,直接的反应是:(算了,算了,不管了,写个DP式子,拿个暴力分得了) 于是对于此题,经过一番分析后,我们可以发现,有好几种方法列出状态及转移方程。
比如说$O(n^3)$的区间DP,读者们是可以较轻松列出的,但是这样的话,我们惊讶的发现:这玩意儿没办法优化,(写的极其优美的情况下)才是$O(n^3)$的。 此种——只适合暴力。
再想想,有没有$O(n^2)$的做法。
当然是有的,我们可以将状态这样设: $f[i]$ 表示装到第$i$个玩具,最小的花费是多少。 这样我们接下来可以直接枚举上一个打包压缩的位置$j$,用$f[j]$来更新我们的$f[i]$,时间复杂度$O(n^2)$。
方程是这样的$f[i]=min{(f[j]+(sum[i]-sum[j]+i-j-l)^2}$ 其中$sum$数组维护的是题目中$c$数组的前缀和(为了计算费用方便)
$!!!$接下来的过程才是重头戏,经过上面dalao博客的学习后,我们知道了斜率优化这个东西,它的本质是要将上面的转移方程转化为 $y=kx+b$的形式,其中$b$就是我们要求的$f[i]$。(方便求)
于是,开始了漫长的导式子过程: (部分摘自上面那位的blog......)
我们可以知道,所有只和$i$有关的项,和常数项可以暂时拿掉(相当于放在$b$旁边),最后再加,因为它是个定值,对答案没有影响,而且在以后的相减过程中,都是可以消去的项(大家不妨可以保留后试一试,看看对答案是否有影响)。
然后呢,我们令 $s[x]=sum[x]+x$
上面那个式子就成了这样: $f[i]=f[j]+s[i]^2+(s[j]-l)^2-2*s[i]*(s[j]-l)$
移项:$f[j]+s[i]^2+(s[j]+l)^2=2*s[i]*(s[j]+l)+f[i]$
省略式子(简化):$f[j]+(s[j]+l)^2=2*s[i]*(s[j]+l)+f[i]$
然后这一坨 $f[j]+(s[j]+l)^2$ 就是 $y$ ,$2*s[i]$就是$k$,$f[i]$便是$b$,$s[j]$便是$x$(至于$l$为什么可以拿掉,因为在维护上面的式子时,我们需要做差,这一过程后,我们发现,$l$消失了。。)。
finally:可以用斜率优化的一贯做法来解题了。 首先要看好,要维护的凸包是向上的,还是向下的。(用人话来说,就是看看这个题是维护最小的花费,还是最大的花费。)然后建个平面直角坐标系,每个点的坐标便是 $(s[j],f[j]+(s[j]+l)^2)$ ,然后。。在此就不赘述了,剩下的上面那篇博客里就都有了,而且也是些套路的问题。
$!!!$这里有个比较重要的问题,就是我们一定一定要看好斜率$k$的符号,以便处理一些细节问题。
以下是代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N=50001; int n, l; long long s[N]; long long f[N]; long long q[N]; double f_x(int x) { return s[x]; } double f_y(int x) { return f[x]+(s[x]+l)*(s[x]+l); } double f_k(int a,int b) { return (f_y(a)-f_y(b))/(f_x(a)-f_x(b)); } int main() { scanf("%d%d",&n,&l); for(int i=1;i<=n;i++) { long long x; scanf("%lld",&x); s[i]=s[i-1]+x; } int head, tail; head=tail=0; for(int i=1;i<=n;i++) s[i]+=i; for(int i=1;i<=n;i++) { while(head<tail&&f_k(q[head],q[head+1])<2*s[i]) head++; f[i]=f[q[head]]+(s[i]-s[q[head]]-l-1)*(s[i]-s[q[head]]-l-1); while(head<tail&&f_k(q[tail],i)<f_k(q[tail],q[tail-1])) tail--; q[++tail]=i; } printf("%lld",f[n]); }
2.[ZJOI2007仓库建设]
// 以下题均为略讲,如有特殊需要注意的地方,会加重处理。
题目链接:https://www.luogu.org/problemnew/show/P2120
呃。。其实我最初分析这道题时,认为和上一道没什么两样。。。(其实真没什么两样),比较裸的概率DP,做起来,代码看起来,都差不多。(未完待续。。。)