斜率优化

斜率优化dp

我之前写的怎么是一坨啊

对于 dp 转移式子类似于 \(f_i=\min(f_j+val(i,j))\) 的形式,其中 \(val(i,j)\) 中含有 \(i\times j\) 的项。

对于 \(\max\) 也同理。

特别地,如果 \(val(i,j)\) 中不含与 \(i\times j\) 有关的项,可以用普通的单调队列优化,这里不再赘述。

看不懂没关系,看例题。

例题

P3195 [HNOI2008] 玩具装箱

\(f_i\) 表示考虑到第 \(i\) 个玩具时的最小费用。

方程:

\[f_i=\min\{f_j+(i-j-1+\sum_{k=j+1}^{i}c_k-L)^2\} \]

我们设 \(sum_i=\sum_{j=1}^i c_j\),即前缀和,\(l=L+1\)。化简式子:

\[f_i=\min\{f_j+(sum_i-sum_j+i-j-l)^2\}\\ \]

\(s_i=sum_i+i\)

\[f_i=\min\{f_j+(s_i-s_j-l)^2\} \]

以下为了简便,我们只考虑 \(\min\) 内的式子。

拆掉平方。

\[f_i=s_i^2-2s_il+f_j+(s_j+l)^2-2s_is_j \]

我们考虑在什么情况下,对于 \(j_1<j_2<i\)\(j_2\) 优于 \(j_1\)

当且仅当满足 \(f_{j_1}+(s_{j_1}+l)^2-2s_i s_{j_1}\ge f_{j_2}+(s_{j_2}+l)^2-2s_i s_{j_2}\)

此时化简重点思路:用含 \(j_1\)\(j_2\) 的式子表示与 \(i\) 有关的式子。

\[f_{j_1}-f_{j_2}+(s_{j_1}+l)^2-(s_{j_2}+l)^2+2s_i(s_{j_2}-s_{j_1})\ge 0\\ 2s_i(s_{j_2}-s_{j_1})\ge (f_{j_2}+(s_{j_2}+l)^2)-(f_{j_1}+(s_{j_1}+l)^2) \]

在这里,我们设 \(a_i=(s_i+l)^2\)\(y_i=f_i+a_i\)\(x_i=s_i\)

式子就转化为了:

\[\frac{y_{j_2}-y_{j_1}}{x_{j_2}-x_{j_1}}\le 2s_i \]

此时这个式子就是个斜率的公式。

所以一个决策点 \(j\),可以看作平面上 \(x=s_j\)\(y=f_j+a_j\) 的点。

所以定义斜率 \(slope(j_1,j_2)=\frac{y_{j_2}-y_{j_1}}{x_{j_2}-x_{j_1}}\)

如果两点间连线的斜率 \(slope(j_1,j_2)\le 2s_i\),那么 \(j_1\) 就可以走人了,因为 \(j_2\) 比它优。

注意(我踩过的坑):只有两个决策点是不可以确定谁优的,必须要根据不同的 \(i\) 决定。

我们考虑有没有什么情况是可以排除点的。

考虑对于三个点 \(i,j,k\),满足如下关系:

其中,\(slope(i,j)=a,slope(j,k)=b,a\ge b\)

我们注意到点 \(j\)

如果 \(j\) 是最优决策,根据上文所述,必然有:

  • \(i\) 比:\(a\le 2s_i\)
  • \(k\) 比:\(b>2s_i\)

那么就与 \(a\ge b\) 矛盾了,所以 \(j\) 在三者中肯定不是最优决策。

所以 \(j\) 就可以滚蛋了。

其实单调队列的本质就是在加入元素前想办法删除队头,加入元素时想办法删除队尾,最后加入新元素。

那么删除队头的判断是哪个呢?就是淘汰掉 \(j_1\) 的式子 \(slope(j_1,j_2)\le 2s_i\)

那么删除中间的 \(j\) 该怎么搞呢?

我们把新元素当作 \(k\),队尾是 \(j\),队尾的前一个是 \(i\)

如果满足条件 \(slope(i,j)\ge slope(j,k)\) 的话,就可以弹出队尾。

最后给个代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ljl;
inline void Rd(auto &num);
const int N=5e4+5;
const ljl inf=1e18;
ljl n,l,c[N],s[N],f[N];
int dq[N],h=1,t=0;
ljl a(int i){return s[i]*s[i]+2*s[i]*l;}
ljl b(int i){return (s[i]-l)*(s[i]-l);}
double y(int j){return (double)f[j]+(double)a(j);}
double x(int j){return (double)s[j];}
double slope(int j1,int j2){return (y(j2)-y(j1))/(x(j2)-x(j1));}

int main(){
	Rd(n);Rd(l);++l;
	for(int i=1;i<=n;++i){
		Rd(c[i]);s[i]=s[i-1]+c[i];f[i]=inf;}
	for(int i=1;i<=n;++i)s[i]+=i;
	dq[++t]=0;
	for(int i=1;i<=n;++i)
	{
		while(h<t&&slope(dq[h],dq[h+1])<=(double)2.0*s[i])
			++h;
		int j=dq[h];
		f[i]=f[j]+a(j)-2*s[i]*s[j]+b(i);
		while(h<t&&slope(dq[t-1],dq[t])>slope(dq[t],i))
			--t;
		dq[++t]=i;
	}
	printf("%lld\n",f[n]);
	return 0;
}
inline void Rd(auto &num)
{
	num=0;char ch=getchar();bool f=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')f=1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		num=(num<<1)+(num<<3)+(ch-'0');
		ch=getchar();
	}
	if(f)num=-num;
	return;
}

FZYZ 寒假集训,花了大半天的时间学的东西。

posted @ 2026-02-02 21:34  Atserckcn  阅读(2)  评论(0)    收藏  举报