斜率优化 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\)
则有:

  1. \(k_0 < k_2 < k_1\),则 \(j_1\) 优于 \(j_2\) 优于 \(j_3\)
  2. \(k_2 \leq k_0 \le k_1\),则 \(j_1,j_3\) 优于 \(j_2\)
  3. \(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;
}
posted @ 2023-03-25 16:20  Symbolize  阅读(39)  评论(0)    收藏  举报