「学习笔记」斜率优化

「HNOI 2008」玩具装箱TOY

首先 O ( n 2 ) O(n^2) O(n2)做法是显然的,使用前缀和然后暴力枚举转移

dp[0] = 0;
for(int i = 1; i <= n; i ++) {
	dp[i] = 1LL << 62;
	for(int j = 0; j < i; j ++) {
		LL x = i - (j + 1) + s[i] - s[j];
		dp[i] = min(dp[i], dp[j] + (x - L) * (x - L));
	}
}

上面的 d p dp dp转移方程为:( s [ k ] = ∑ i = 1 k c [ i ] s[k]=\sum_{i=1}^kc[i] s[k]=i=1kc[i] 为前缀和)

d p [ i ] = m i n ( d p [ j ] + ( i − j − L − 1 + s [ i ] − s [ j ] ) 2 ) &ThickSpace; ( 0 ≤ j &lt; i ) dp[i]=min(dp[j]+(i - j - L - 1 + s[i] - s[j]) ^ 2) \; (0\leq j &lt; i) dp[i]=min(dp[j]+(ijL1+s[i]s[j])2)(0j<i)

a i = s [ i ] + i − L − 1 , b j = s [ j ] + j a_i=s[i]+i-L-1,b_j=s[j]+j ai=s[i]+iL1,bj=s[j]+j,则:

d p [ i ] = m i n ( d p [ j ] + ( a i − b j ) 2 ) &ThickSpace; ( 0 ≤ j &lt; i ) dp[i]=min(dp[j]+(a_i - b_j) ^ 2) \; (0\leq j &lt; i) dp[i]=min(dp[j]+(aibj)2)(0j<i)

假设选 j j j转移: d p [ i ] = d p [ j ] + ( a i − b j ) 2 dp[i]=dp[j]+(a_i - b_j) ^ 2 dp[i]=dp[j]+(aibj)2

d p [ i ] = d p [ j ] + a i 2 + b j 2 − 2 a i b j dp[i]=dp[j]+a_i^2 + b_j^2 -2a_ib_j dp[i]=dp[j]+ai2+bj22aibj

移项得:

2 a i b j + d p [ i ] − a i 2 = d p [ j ] + b j 2 2a_ib_j+dp[i]-a_i^2 =dp[j]+ b_j^2 2aibj+dp[i]ai2=dp[j]+bj2

把这看成是 k x + b = y kx+b=y kx+b=y的直线形式,则其斜率为 2 a i 2a_i 2ai x = b j x=b_j x=bj y = d p [ j ] + b j 2 y=dp[j]+ b_j^2 y=dp[j]+bj2,截距 d p [ i ] − a i 2 dp[i]-a_i^2 dp[i]ai2

也就是说假设我们用 j j j转移, y = 2 a i x + b y=2a_ix+b y=2aix+b这条直线经过点 P j ( b j , d p [ j ] + b j 2 ) P_j(b_j,dp[j]+ b_j^2) Pj(bj,dp[j]+bj2)时,它的截距 + a i 2 +a_i^2 +ai2就是 d p [ i ] dp[i] dp[i]

这样我们每次选择一个最优的 j j j转移就行了

显然是找一个斜率为 2 a i 2a_i 2ai、经过 P j P_j Pj,截距最小的 j j j

根据图像,这些最优的 j j j点是会构成一个下凸包的

然后每次找到第一个满足的位置就是最优答案(截距最小),如图

s l o p e ( P 1 , P 2 ) slope(P_1,P_2) slope(P1,P2)表示经过 P 1 , P 2 P1,P2 P1,P2的直线斜率

可以发现最优解处就是找到凸包上的第一个满足 s l o p e ( P j , P j + 1 ) &gt; 2 a i slope(P_j,P_{j+1})&gt;2a_i slope(Pj,Pj+1)>2ai的点 j j j

注意每次需要查找的 a i = s [ i ] + i − L − 1 a_i=s[i]+i-L-1 ai=s[i]+iL1是递增的,因为 s [ i ] + i s[i]+i s[i]+i递增, − L − 1 -L-1 L1是常量

因此可以使用单调队列维护凸包

考虑队首:根据单调性,若 s l o p e ( P j , P j + 1 ) &lt; 2 a i slope(P_j,P_{j+1})&lt;2a_i slope(Pj,Pj+1)<2ai P j P_j Pj就可以以后都不再考虑,把它出队 ( pop_front ) ( \text{pop\_front}) (pop_front)

这样就可以更新 d p [ i ] dp[i] dp[i]了。

再考虑队末:把 P i ( b i , d p [ i ] + b i 2 ) P_i(b_i,dp[i]+ b_i^2) Pi(bi,dp[i]+bi2)加入凸包。如图所示,红线优于绿线,因此 P b a c k − 1 P_{back-1} Pback1将改为连 P i P_i Pi

因此单调队列具体做法是:记末端 2 2 2个点分别为 P b a c k − 1 , P b a c k P_{back-1},P_{back} Pback1,Pback

s l o p e ( P b a c k − 1 , P b a c k ) &gt; s l o p e ( P b a c k − 1 , P i ) slope(P_{back-1},P_{back})&gt;slope(P_{back-1},P_i) slope(Pback1,Pback)>slope(Pback1,Pi),从队尾弹出 ( pop_back ) (\text{pop\_back}) (pop_back)

直到只剩一个点或者不满足大于条件时插入。

#include <cstdio>

typedef long long LL;

const int N = 5e4 + 10; 

int n, L, c[N];
LL dp[N], s[N], a[N], b[N];

inline LL x(int i) { return b[i]; }
inline LL y(int i) { return dp[i] + b[i] * b[i]; }
inline double slope(int i, int j) { return (y(j) - y(i)) / (double) (x(j) - x(i)); }

int main() {
	scanf("%d%d", &n, &L);
	for(int i = 1; i <= n; i ++) {
		scanf("%d", &c[i]);
		s[i] = s[i - 1] + c[i];
		a[i] = (b[i] = s[i] + i) - L - 1;
	}
	static int q[N], hd = 0, bk = 0;
	q[bk ++] = 0; dp[0] = a[0] = b[0] = 0; //注意队列初始时应 push(j = 0)
	for(int i = 1; i <= n; i ++) {
		for(; hd + 1 < bk && slope(q[hd], q[hd + 1]) < 2.0 * a[i]; ++ hd) ;
		dp[i] = dp[q[hd]] + (a[i] - b[q[hd]]) * (a[i] - b[q[hd]]);
		for(; hd < bk - 1 && slope(q[bk - 2], q[bk - 1]) > slope(i, q[bk - 1]); -- bk) ;
		q[bk ++] = i;
	}
	printf("%lld\n", dp[n]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值