斜率优化 DP
试用范围
形似:\(f_i=\min\limits_{j=1}^{i} a_ib_j + c_j + d_i\) 或 \(\max\limits_{j=1}^{i}a_ib_j + c_j + d_i\) 的状态转移方程(\(b\) 单调递增),因为此时在 \(a_i \times b_j\) 中 \(i,j\) 皆有,所以不能使用单调队列优化 DP。
理解与证明
前置
以 Luogu P3195 [HNOI2008]玩具装箱 为例。
设 \(s_n=\sum\limits_{i=1}^nc_i+1\),则有 \(f_i=\min\limits_{f_j+(s_i-s_j-L)^2}\)
先让 \(L = L + 1\)。
去掉最小值,则有 \(f_i=f_j+(s_i-s_j-L)^2\)。
化简得: \(f_i = f_j + s_i^2 - 2s_iL + (s_j+l)^2-2s_is_j\)
理解
变式得:\(f_i = (-2s_is_j) + (f_j + (s_j + L)^2) + (s_i^2 - 2s_iL)\)。
维护一个下凸包。
\[令:$j_1,j_2$ 满足 0 \leq j_1 < j_2 < i 两个 i 的决策点且 j_2 优于 j_1
\]
\[则有:(-2s_is_{j_2})+(f_{j_2}+(s_{j_2}+L)^2)+(s_i^2-2s_iL) \leq (-2s_is_{j_1})+(f_{j_1}+(s_{j_1}+L)^2)+(s_i^2-2s_iL)
\]
\[即:(-2s_is_{j_2})+(f_{j_2}+(s_{j_2}+L)^2) \leq (-2s_is_{j_1})+(f_{j_1}+(s_{j_1}+L)^2)
\]
\[\therefore -2s_i(s_{j_2}-s_{j_1}) \leq (f_{j_1} + (s_{j_1}+L)^2)-(f_{j_2}+(s_{j_2}+L)^2)
\]
\[\because c_j \geq 1
\]
\[\therefore s_{j+1} > s_j
\]
\[又\because j_2 > j_1
\]
\[\therefore s_{j_2}-s_{j_1} > 0
\]
\[\therefore 2s_i \geq \frac{(f_{j_2}+(s_{j_2}+L)^2)-(f_{j_1}+(s_{j_1}+L)^2)}{s_{j_2}-s_{j_1}}
\]
\[令:x(j)=s_j,y(j)=f_j+(s_j+L)^2,点P(j)=P(x(j),y(j))=P(s_j,f_j+(s_j+L)^2)
\]
\[\therefore 2s_i \geq \frac{y(j_2)-y(j_1)}{x(j_2)-x(j_1)}
\]
\[显然不等式右边是关于点 P(j_1) 与点 P(j_2) 连线所得函数图像的斜率 k
\]
也就是说,如果有 \(k \leq 2s_i\),则 \(j_2\) 优于 \(j_1\)。
例:\(A(j_1),B(j_2),C(j_3),k_1,k_2\) 在图中。

令:\(k_0=2s_i\)
\(\because k_2 < k_1\)
则有:
- \(k_0 < k_2 < k_1\),则 \(j_1\) 优于 \(j_2\) 优于 \(j_3\)。
- \(k_2 \leq k_0 \le k_1\),则 \(j_1,j_3\) 优于 \(j_2\)。
- \(k_2 < k_1 \leq k_0\),则 \(j_3\) 优于 \(j_2\) 优于 \(j_1\)。
所以无论如何,\(j_2\) 都不是最优解,所以我们在之后可以不管它。

这个过程,也就是维护凸包。
简单画一个:

其中红线是凸包外壳(也就是最后维护凸包的单调队列里面的),青线为之前最优,但之后被淘汰(弹出队列)的斜率。
最后,由于凸包的斜率具有单调性,二分查找即可。
代码
AC Code of Luogu P3195 [HNOI2008]玩具装箱
/*
f[i]=f[j]+(i-j-1+c[i]-c[j]-L)^2=f[j]+((c[i]+i)-(c[j]+j)-(L+1))^2;
令:s[i]=c[i]+i,++L
则有: f[i]=f[j]+(s[i]-s[j]-L)^2=f[j]+((s[i]-L)-s[j])^2=f[j]+(s[i]-L)^2+s[j]^2-2(s[i]-L)*s[j]
移项得:f[j]+s[j]^2=2(s[i]-L)*s[j]+f[i]-(s[i]-L)^2
令:y=f[j]+s[j]^2,kx=2(s[i]-L)*s[j],b=f[i]-s(s[i]-L)^2
*/
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define x first
#define y second
#define rep1(i,l,r) for(int i=l;i<=r;i++)
#define rep2(i,l,r) for(int i=l;i>=r;i--)
const int N=1e5+10;
using namespace std;
int n,L,hh,tt,q[N];
double s[N],f[N];
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f*x;
}
double x(int i){return s[i]+i+L;}
double y(int i){return f[i]+x(i)*x(i);}
double k(int i,int j){return (y(i)-y(j))/(x(i)-x(j));}
signed main()
{
n=read();
L=read()+1;
rep1(i,1,n)
{
cin>>s[i];
s[i]+=s[i-1];//前缀和
}
hh=tt=1;//维护凸包
rep1(i,1,n)
{
while(hh<tt&&k(q[hh],q[hh+1])<2*(s[i]+i)) ++hh;//当前队头能否移除
f[i]=f[q[hh]]+(s[i]+i-x(q[hh]))*(s[i]+i-x(q[hh]));//优化DP
while(hh<tt&&k(i,q[tt-1])<k(q[tt-1],q[tt])) --tt;//当前队尾能否移除
q[++tt]=i;//此点入队
}
cout<<(int)f[n]<<endl;
return 0;
}

浙公网安备 33010602011771号