斜率优化
前置技能点:
- DP初步
- 单调队列
- 求凸包
闲话:
前面写这么多都是为了写这个做准备的…本来还应该写一下凸包,但凸包算法比较多,迟点吧…
问题:
给定非负整数序列 a a a长度为 n n n,状态转移方程为 D P [ i ] = min k = 1 i ( D P [ k ] + ( s u m [ i ] − s u m [ k ] ) 2 + C ) DP[i]=\min_{k=1}^{i}(DP[k]+(sum[i]-sum[k])^2+C) DP[i]=mink=1i(DP[k]+(sum[i]−sum[k])2+C),求DP[n]
思路:
最朴素的做法,有状态方程就对着做, f o r ( i ) for(i) for(i)套 f o r ( k ) for(k) for(k),复杂度 O ( n 2 ) O(n^2) O(n2)
概念引入:
设 D P k [ i ] = D P [ k ] + ( s u m [ i ] − s u m [ k ] ) 2 + C DP_k[i]=DP[k]+(sum[i]-sum[k])^2+C DPk[i]=DP[k]+(sum[i]−sum[k])2+C, j < p j<p j<p且 D P p [ i ] DP_p[i] DPp[i]≤ D P j [ i ] DP_j[i] DPj[i]
D P j [ i ] ≥ D P p [ i ] DP_j[i]≥DP_p[i] DPj[i]≥DPp[i]
D P [ j ] + ( s u m [ i ] − s u m [ j ] ) 2 + C ≥ D P [ p ] + ( s u m [ i ] − s u m [ p ] ) 2 + C DP[j]+{(sum[i]-sum[j])}^2+C≥DP[p]+{(sum[i]-sum[p])}^2+C DP[j]+(sum[i]−sum[j])2+C≥DP[p]+(sum[i]−sum[p])2+C 拆开
D P [ j ] − 2 ∗ s u m [ i ] ∗ s u m [ j ] + s u m [ j ] 2 ≥ D P [ p ] − 2 ∗ s u m [ i ] ∗ s u m [ p ] + s u m [ p ] 2 DP[j]-2*sum[i]*sum[j]+{sum[j]}^2≥DP[p]-2*sum[i]*sum[p]+{sum[p]}^2 DP[j]−2∗sum[i]∗sum[j]+sum[j]2≥DP[p]−2∗sum[i]∗sum[p]+sum[p]2 拆开平方项并消去常数 s u m [ i ] 2 sum[i]^2 sum[i]2和 C C C
( D P [ j ] + s u m [ j ] 2 ) − ( D P [ p ] + s u m [ p ] 2 ) ≥ 2 ∗ s u m [ i ] ∗ ( s u m [ j ] − s u m [ p ] ) (DP[j]+{sum[j]}^2)-(DP[p]+{sum[p]}^2)≥2*sum[i]*(sum[j]-sum[p]) (DP[j]+sum[j]2)−(DP[p]+sum[p]2)≥2∗sum[i]∗(sum[j]−sum[p]) 移项
设 f ( x ) = D P [ x ] + s u m [ x ] 2 f(x)=DP[x]+{sum[x]}^2 f(x)=DP[x]+sum[x]2, g ( x ) = 2 ∗ s u m [ x ] g(x)=2*sum[x] g(x)=2∗sum[x], h ( x ) = s u m [ x ] h(x)=sum[x] h(x)=sum[x]
则原式等价于 f ( j ) − f ( p ) ≥ h ( i ) ∗ ( g ( j ) − g ( p ) ) f(j)-f(p)≥h(i)*(g(j)-g(p)) f(j)−f(p)≥h(i)∗(g(j)−g(p))
由于g(x)单调不降
两边同除以 ( g ( j ) − g ( p ) ) (g(j)-g(p)) (g(j)−g(p))(事实上可能为0,不能除,这里是为了看上去直观一点)得到:
f ( j ) − f ( p ) g ( j ) − g ( p ) ≤ h ( i ) \frac{f(j)-f(p)}{g(j)-g(p)}≤h(i) g(j)−g(p)f(j)−f(p)≤h(i)
而由于 h ( i ) h(i) h(i)单调不降, g ( j ) − g ( p ) ≤ 0 g(j)-g(p)≤0 g(j)−g(p)≤0故当 i i i增大时,不等式恒成立
也即是一旦对于 D P [ i ] DP[i] DP[i],不等式成立,则对于任意 i ′ > i i'>i i′>i不等式恒成立,即决策点p永远优于决策点j
定义使得 D P k [ x ] DP_k[x] DPk[x]取得最小值的点 k k k为 p [ x ] p[x] p[x](perfect)。
这意味着,对于要得到的 D P [ i ] DP[i] DP[i],我们只需要将 p [ i − 1 ] p[i-1] p[i−1]到 i − 1 i-1 i−1的所有元素遍历一遍即可得到 p [ i ] p[i] p[i]。
目前还属于常数剪枝,不影响复杂度…
现在,以 g ( x ) g(x) g(x)为 x x x轴, f ( x ) f(x) f(x)为 y y y轴建立直角坐标系,由于 g ( x ) g(x) g(x)递增,故输入的每个元素的顺序与其在x轴上的顺序相同。
将所有下标为 p [ i − 1 ] p[i-1] p[i−1]到 i − 1 i-1 i−1的点画到图上,方便起见,就画三个点 l , m , r l,m,r l,m,r。
回顾之前的式子
f ( j ) − f ( p ) g ( j ) − g ( p ) ≤ h ( i ) \frac{f(j)-f(p)}{g(j)-g(p)}≤h(i) g(j)−g(p)f(j)−f(p)≤h(i)
不难发现左边部分在这个直角坐标系上就是 j , p j,p j,p两点间的斜率。
设 k 1 = k ( l , m ) k_1=k(l,m) k1=k(l,m), k 2 = k ( m , r ) k_2=k(m,r) k2=k(m,r), k 3 = k ( l , r ) k_3=k(l,r) k3=k(l,r)。
一共有两种情况:
-
k 1 ≥ k 2 k_1≥k_2 k1≥k2
-
k 1 < k 2 k_1<k2 k1<k2
我们分开来讨论 -
情况 1
k 1 ≥ k 3 ≥ k 2 k_1≥k_3≥k_2 k1≥k3≥k2
对于一个给定的 h ( i ) h(i) h(i),若
h ( i ) ≥ k 1 h(i)≥k_1 h(i)≥k1,则 m m m点比 l l l点更优,但同时 h ( i ) ≥ k 2 h(i)≥k_2 h(i)≥k2,故 r r r点在三点中最优。
k 1 > h ( i ) ≥ k 3 k_1>h(i)≥k_3 k1>h(i)≥k3,则 r r r点比 l l l点更优,同时 h ( i ) ≥ k 2 h(i)≥k_2 h(i)≥k2,故 r r r点在三点中最优。
k 3 > h ( i ) ≥ k 2 k_3>h(i)≥k_2 k3>h(i)≥k2,则 r r r点比 m m m点更优,但 h ( i ) < k 3 h(i)<k_3 h(i)<k3,故 l l l点在三点中最优。
k 2 > h ( i ) k_2>h(i) k2>h(i),则 l l l点在三点中最优。
也即是当出现这种情况时, m m m点根本没有判定的必要,只需要判定 k ( l , r ) k(l,r) k(l,r)与 h ( i ) h(i) h(i)的关系即可。 -
情况 2
k 2 > k 3 > k 1 k_2>k_3>k_1 k2>k3>k1
对于一个给定的 h ( i ) h(i) h(i),若
h ( i ) ≥ k 2 h(i)≥k_2 h(i)≥k2,则 r r r点比 m m m点更优,同时 h ( i ) ≥ k 1 h(i)≥k_1 h(i)≥k1,故 r r r点在三点中最优。
k 2 > h ( i ) ≥ k 3 k_2>h(i)≥k_3 k2>h(i)≥k3,则 r r r点比 l l l点更优,但 h ( i ) < k 2 h(i)<k_2 h(i)<k2,故 m m m点在三点中最优。
k 3 > h ( i ) ≥ k 1 k_3>h(i)≥k_1 k3>h(i)≥k1,则 m m m点比 l l l点更优,同时 h ( i ) < k 2 h(i)<k_2 h(i)<k2,故 m m m点在三点中最优。
k 1 > h ( i ) k_1>h(i) k1>h(i),则 l l l点在三点中最优。
也即是当出现这种情况时,我们不需要考虑 k ( l , r ) k(l,r) k(l,r),只需要考虑相邻两点之间的斜率即可。
得到了上面的结论后,我们可以用水平法求下凸包进行优化,但是点
p
[
i
−
1
]
p[i-1]
p[i−1]可能会因为
i
−
1
i-1
i−1的进入而在做下凸包时被删除。
所以我们需要知道新加的元素导致凸包尾被删除到了哪个点,在进行
p
[
i
]
p[i]
p[i]的求值时,以此判断是从
p
[
i
−
1
]
p[i-1]
p[i−1]还是直接从单调栈中最后两个元素开始对斜率进行判定。
此外还有另一个办法,我们注意到即使存在点 j < p [ i − 1 ] < i − 1 j<p[i-1]<i-1 j<p[i−1]<i−1使得出现之前讨论过的情况1,但实际上从点 p [ i − 1 ] p[i-1] p[i−1]到点 i − 1 i-1 i−1的斜率一样足以让我们判定点 i − 1 i-1 i−1优于点 p [ i − 1 ] p[i-1] p[i−1]( h ( i ) ≥ h ( i − 1 ) ≥ k 1 h(i)≥h(i-1)≥k_1 h(i)≥h(i−1)≥k1)
所以我们可以通过直接移动栈底,即单调队列的弹出队头操作来维护得到 p [ i ] p[i] p[i]到 i − 1 i-1 i−1之间的下凸包,而非从 1 1 1到 i − 1 i-1 i−1之间的下凸包。
两个方法的代码几乎没有不同,第二个可能快点吧,但第一种方法能顺便得到下凸包。
复杂度:
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)