斜率优化dp学习笔记

Posted dddddadaplllllane

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了斜率优化dp学习笔记相关的知识,希望对你有一定的参考价值。

例题:

洛谷P2900 [USACO08MAR]Land Acquisition G

分析与转化

可以发现,有一些东西是完全没用的

当一个矩形的长和宽都比另一个矩形小的时候,这个矩形就是的,因为他完全可以套在另外那个矩形一起买

这时候我们就能发现:我们得到了一个长度递减,宽度递增的矩形序列

而要求的就是使得在其划分出一些段之后,总代价最小

于是乎我们可以列出方程(显然这是dp):

$ dp_i = max (dp_j + w_j+1 \\times h_i) $

于是我们就能够 $ O (n^2) $ 的解决了

套用斜率优化

划重点:

斜率优化通用式子:

$ b = y - kx $

其中 \\(b\\)是我们想要知道的,\\(y,x\\) 都为前面的“转移项” \\(k\\) 为当前要求的“特有”的系数

我们必须要保证x是递增的,k随意,b我们这里希望他最小


我们观察式子,发现只有一个项与 \\(i\\)\\(j\\) 都有关

于是我们令:

\\(x=-w_j+1\\) (为了保证递增)

\\(y=dp_j\\)

\\(k=h_i\\)

\\(b=dp_i\\)

可以发现,我们这样设完了以后,就能够把转移看作:

在一个二维数点当中,有一条直线已经定了斜率,要求截距最小

如图:

所以我们就能够通过维护下凸壳,并在下凸壳上二分来求得转移位置了


补充

还有一个情况,当你化成二维数点以后要求截距最大

这时候你就需要维护上凸壳

于是上下凸壳的区分就是这么来的

代码实现

补充:有时候h不一定像这样单调的,所以必须二分,一下是兼容性比较强的代码(板子):

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e4+10;
struct modify 
	ll w,h;
;
modify a[N],c[N];
bool cmp(modify x,modify y) 
	if(x.w==y.w) return x.h>y.h;
	else return x.w>y.w;

int n,cnt;
ll dp[N];
/*
define:
x: -w_j+1
k: h_i
y: dp_j
b: dp_i
*/
int st[N],sttop;
int find(ll k) 
	int l=1,r=sttop-1,res=sttop;//注意边界
	while(l<=r) 
		int mid=(l+r)/2;
		if((dp[st[mid+1]]-dp[st[mid]])>=k*(-c[st[mid+1]+1].w+c[st[mid]+1].w))//二分&&除法转乘法
			res=mid;r=mid-1;
		 else 
			l=mid+1;
		
	
	return st[res];

int main() 
	cin>>n;
	for(int i=1;i<=n;i++) 
		cin>>a[i].w>>a[i].h;
	
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++) 
		if(a[i].h>c[cnt].h)
			c[++cnt]=a[i];
	
	st[++sttop]=0;//注意边界
	for(int i=1;i<=cnt;i++) 
		int j=find(c[i].h);
		dp[i]=dp[j]+c[j+1].w*c[i].h;//按照原来的方程转移(切记不要直接用x,y)
		while(sttop&&
		(dp[st[sttop]]-dp[st[sttop-1]])*(-c[i+1].w+c[st[sttop]+1].w) >
		(dp[i]-dp[st[sttop]])*(-c[st[sttop]+1].w+c[st[sttop-1]+1].w))//同样
			sttop--;
		st[++sttop]=i;
	
	cout<<dp[cnt];
	return 0;

于是这题就完成了

题目总结

  1. 把原来方程转化为上述式子,使得:x递增(关键!!)

  2. 在二分和推单调栈的时候,一定要手画出边界,不然会错

  3. 涉及除法判断时,尽可能转long long,避免丢精度

笔记篇斜率优化dp HNOI2008玩具装箱

斜率优化dp

本来想直接肝这玩意的结果还是被忽悠着做了两道数论
现在整天浑浑噩噩无心学习甚至都不是太想颓废是不是药丸的表现
各位要知道我就是故意要打删除线并不是因为排版错乱
反正就是一个del标签嘛并不是什么大事的说
讲道理这一篇要不是写laTex我就直接用html写了
Emmmm划掉的原因是因为跟正题一点关系都没有啊
不让自己写摘要我写第一段凑摘要好咯
第一次写花花绿绿的blog感觉还是很新鲜的
你看看我到了正文部分还划不划啊(该划的还是划╭(╯^╰)╮)
其实文章里有彩蛋比如这里 被你发现了OvO 哈哈
据说找到了所有的彩蛋能获得一些奖励(但很不幸这是假的
不过laTex公式也可以带颜色
这一段对的如此不整齐的原因可能是为了逼死强迫症
明明是懒得把字数扣成一样的非要找这么个冠冕堂皇的借口么

上次我们说过
\[ f[i]=max\{f[j]+x[j]\}(j\in[1,i)) \]
这样的方程的优化(变量记录)和
\[ f[i]=max\{f[j]+x[j]\} (j\in[i-m,i)) \]
这样的方程的优化(单调队列), 但是如果遇到
\[ f[i]=max\{f[j]+g(i,j)\} (j\in[1,i) \]
这样的方程, 就不会处理了... 这也就是今天要讲的斜率优化dp.
对于这种方程, 我们考虑将这个式子化成\(y\)=\(k\)\(x\)+\(b\)的形式, 其中\(y\)\(x\)是只和\(j\)相关的式子, \(k\)是和\(i\)相关的式子, \(b\)\(f[i]\)和其他什么常数, 然后根据类似于线性规划的知识维护一定的单调性来优化转移.

光这么讲也没什么意思嘛(鬼能看懂咯), 我们看例题. 其实就是bzoj1010 第一页就有 传送门在这里~

状态转移方程比较显然: 令\(f[i]\)表示装前\(i\)个东西的最小花费, \(sum[i]\)表示\(C_i\)的前缀和, 则
\[ f[i]=min\{f[j]+(sum[i]-sum[j]+i-j-1-L)^2\} \]
这样我们已经可以\(O(n^2)\)做了, 不过并没有给部分分, 不知道能得几分, 反正\(n\leq5W\)想A掉是不可能的.这辈子都是不可能的. dp优化以后跑得又快, 又可以装逼, 我超喜欢优化的!

所以很明显需要优化. 那么就是说要化式子.我讨厌化式子!!!
为了方便, 我们令\(s[i]=sum[i]+i,C=L+1\), 那么
\[f[i]=f[j]+(s[i]-s[j]-C)^2=f[j]+(s[i]-C)^2-2(s[i]-C)*s[j]+s[j]^2\]
然后移项, 得到
\(f[j]+s[j]^2\)=\(2(s[i]-C)\)*\(s[j]\)+\(f[i]-(s[i]-C)^2\)
这样就出现了\(y\)=\(k\)\(x+\)\(b\)的形式. 根据线性规划一类的知识, 我们要在可行域中最小化\(f[i]\), 就是要最小化\(截距b\). 话说化带颜色的式子的时候的laTex也是挺好看的..
那么怎么最小化截距呢? 我们画个图. 画的很丑大家轻喷...
技术分享图片
首先很明显我们dp的时候求好\(t\)的状态可以存在一个点\((x,\)\(y\)\()\)里面, 即\((s[t],\)\(f[t]-s[t]^2\)\()\). 这样我们每次就拿一条斜率为\(2(s[i]-C)\)的直线去里面找截距的最小值就行了. 但这样还是\(O(n^2)\)的, 因为我们没有充分利用单调性来去掉那些根本不可能优的点.
技术分享图片Emmmm其实这张图画得更草率OvO
我们可以看到 如果将要插入的点是红色的, 那么无论斜率怎样, 它都不会比已经加入过的点优(截距更小), 我们就不需要考虑红色点了, 但是蓝色的点则是可能更优的, 那么我们就要保留蓝色点.
其实这里要证明决策单调性什么的 但画图就显得很直观 显然成立我们就不证了(明明是因为懒←_←
经过若干个点的验证, 我们发现, 可能更优的点都集中在下凸壳上!
那么我们维护下凸壳就好了. 这样的话转移的复杂度似乎从\(O(n)\)降到了\(O(H)\), 但似乎还是该怎么过不去就怎么过不去.
所以继续考虑, 为什么转移的时候非要遍历所有点呢?

我们惊奇地发现, 这个题有一个性质, 就是斜率\(k=2(s[i]-C)\)递增的, 所以吧,
技术分享图片
我们枚举下一个斜率的时候, 可以发现, 如果这个点与下一个点的连线的斜率比枚举的斜率小(前两个点), 那么截距就会比下一个点大, 而且由于斜率递增, 所以差距会越来越大, 那这个点我们就可以不要了. 所以我们就可以看出可以用一个单调队列来维护这个凸壳, 每次先把队首不合法的弹出, 然后取队首转移即可. 其实这里关于斜率的讨论应该是要化一波覆盖的式子的, 但是也是由图显然, 而且做半平面交的时候化过, 这里就不化了(显然还是因为懒←_←

这样的话转移的时间复杂度就从\(O(H)\)降到了\(O(1)\), 总复杂度也就降到了\(O(n)\)的水平, 就可以非常愉快的通过此题啦~ 所以不是很懂一个正解\(O(n)\)的题目给个\(n\leq5W\)的数据范围是几个意思...

惊奇地发现自己调了一个晚上的原因竟然是化式子移项正负号反了??? 应该打回小学重造.
本来想用叉积结果化出来的式子太臃肿 我当时还不知道自己式子化错了然后不好调就删掉改成斜率了.
非常不喜欢这样损精度的方式但其实都是整数所以还好, 但是后来发现式子化错了之后懒得改就这么写下去了...
有一点就是5W*1e7要开long long.. 其实代码非常简单... 就这么几行你还调了一晚上←_←

#include <cmath>
#include <cstdio>
const int N=5e4+5;
typedef long long LL;
inline int gn(int a=0,char c=0){
    for(;c<'0'||c>'9';c=getchar());
    for(;c>47&&c<58;c=getchar())a=a*10+c-48;return a;
}
LL s[N],f[N];int q[N],h=0,t=0,n,l;
double slope(int x,int y){return 1.0*(f[x]+s[x]*s[x]-f[y]-s[y]*s[y])/(s[x]-s[y]);}
int main(){
    n=gn(),l=gn()+1;
    for(int i=1;i<=n;++i) s[i]=s[i-1]+gn()+1;
    for(int i=1;i<=n;++i){
        while(h<t&&slope(q[h],q[h+1])<=2*(s[i]-l)) ++h;
        f[i]=f[q[h]]+(s[i]-s[q[h]]-l)*(s[i]-s[q[h]]-l);
        while(h<t&&slope(q[t],q[t-1])>=slope(i,q[t])) --t;
        q[++t]=i;
    } printf("%lld",f[n]);
}

我觉得代码写的非常清楚了 就这样吧...
怎么样,找到所有透明的字了嘛?? 哈哈哈~

以上是关于斜率优化dp学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

单调性优化学习笔记

笔记篇斜率优化dp SDOI2016征途

笔记篇斜率优化dp ZJOI2007仓库建设

斜率优化DP学习

笔记篇斜率优化dp APIO2010特别行动队

笔记篇单调队列优化dp学习笔记&&luogu2569_bzoj1855股票交♂易