斜率优化dp
写在前面:
表面上我是有斜率的 \(O(n),o(n \log n),o(n\log^2n)\)。
其实统统是 \(O(n \log n)\) 的李超树版子,嘻嘻。
下面是正文:
斜率优化,一种dp的优化方式,找到一个式子为 \(y=k*x+b\) 的玩意时就可以使用。
通过例题讲解:
你发现,你只会取连续的一段,而且不会限制你最后取了多少段,每一段在取了后其值就不会发生变化了.
然后这个一段的战斗力 \(X\) 显然可以作前缀和, \(X=Sum_r-Sum_l\)
那么基础的dp式子就很明显了:
第一个式子一定要看懂!
\[f_i=\min_{j<=i}f_j+aX^2+bX+c
\]
\[=f_j+a(sum_i-sum_j)^2+b(sum_i-sum_j)+c
\]
\[=f_j+a(sum_i^2+sum_j^2-2sum_i*sum_j)+b(sum_i-sum_j)+c
\]
\[=f_j+a*sum_i^2+a*sum_j^2-2a*sum_i*sum_j+b*sum_i-b*sum_j+c
\]
\[=(-2a*sum_i*sum_j)+(f_j+a*sum_j^2-b*sum_j)+(a*sum_i^2+b*sum_i+c)
\]
你发现把式子变成了三个部分:
- 又有 \(i\) 又有 \(j\) 的
- 只有 \(j\) 的
- 有了 \(i\) 就可以 \(O(1)\) 直接算的(前面怎么变其都不会变)
你在发现一下,实际上对于一个数 \(sum_i\) 影响其取值的就是这个数乘上一个数在加上一个固定的数
很明显了, \(y=kx+b\) 其中y是取值最大值
\[**是一条直线!**
\]
想一想李超线段树是干嘛的)
维护区间最大的线段!
你把这个玩意维护出来,不就做完了).管你什么单增,什么单减,什么不增,什么凸包,和我的轮椅说去吧!
每次更新了 \(f_i\) 又把 \(f_i\) 这条直线的斜率更新出来,再做下一个的查询
再记录一下我的李超树版子:
struct Node{
lb k,b;//取特定位置的值
inline lb gy(int x){return (lb)x*k+b;}
}t[M];//定义函数
inline void upt(int id,int l,int r,Node New){
int Mid=(l+r)>>1;//中间
//左和中间互相比大小
bool ql=New.gy(l)>t[id].gy(l),
qm=New.gy(Mid)>t[id].gy(Mid);
//如果新的在中间更优,这个变成新的,流放旧的
if(qm) std::swap(t[id],New);
if(l==r) return void();
//qm=1 流放原来的
//ql=0 原来的在左边大,滚去更新左边
//ql=1 原来的在左边小,滚去更新右边
//qm=0 现在的接着流放
//ql=0 现在的在左边小,滚去更新右边
//ql=1 原来的在左边大,滚去更新左边
if(ql!=qm) upt(ls,l,Mid,New);
else upt(rs,Mid+1,r,New);
}
inline lb ask(int id,int l,int r,int x){
if(l==r) return t[id].gy(x);
int Mid=(l+r)>>1;
//基础定义往下搜
if(x<=Mid) return max(t[id].gy(x), ask(ls,l,Mid,x));
else return max(t[id].gy(x), ask(rs,Mid+1,r,x));
}
一些练手的题
记得一些题要离散化)
- P3628 [APIO2010] 特别行动队
版子 - P3648 [APIO2014] 序列分割
版子 - P2120 [ZJOI2007] 仓库建设
版子 - P4360 [CEOI 2004] 锯木厂选址
你去搜一下\(f_1,f_2,f_3\) 表示在这里建第几个工厂的最小花销,反正最多冲两个工厂 - P4072 [SDOI2016] 征途
方差式子自己推,发现最后的那个答案式子会非常简单 - P2900 [USACO08MAR] Land Acquisition G
以max排序推狮子,要注意自己娶自己