斜率优化dp

写在前面:

表面上我是有斜率的 \(O(n),o(n \log n),o(n\log^2n)\)
其实统统是 \(O(n \log n)\) 的李超树版子,嘻嘻。

下面是正文:

斜率优化,一种dp的优化方式,找到一个式子为 \(y=k*x+b\) 的玩意时就可以使用。
通过例题讲解:

  1. P2120 [ZJOI2007] 仓库建设
    image
    image

你发现,你只会取连续的一段,而且不会限制你最后取了多少段,每一段在取了后其值就不会发生变化了.

然后这个一段的战斗力 \(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是取值最大值

\[**是一条直线!** \]

想一想李超线段树是干嘛的)
image

维护区间最大的线段!
你把这个玩意维护出来,不就做完了).管你什么单增,什么单减,什么不增,什么凸包,和我的轮椅说去吧!

每次更新了 \(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));
}

一些练手的题

记得一些题要离散化)

  1. P3628 [APIO2010] 特别行动队
    版子
  2. P3648 [APIO2014] 序列分割
    版子
  3. P2120 [ZJOI2007] 仓库建设
    版子
  4. P4360 [CEOI 2004] 锯木厂选址
    你去搜一下\(f_1,f_2,f_3\) 表示在这里建第几个工厂的最小花销,反正最多冲两个工厂
  5. P4072 [SDOI2016] 征途
    方差式子自己推,发现最后的那个答案式子会非常简单
  6. P2900 [USACO08MAR] Land Acquisition G
    以max排序推狮子,要注意自己娶自己
posted @ 2025-10-20 21:28  rerecloud  阅读(1)  评论(0)    收藏  举报