BZOJ 1010: [HNOI2008] 玩具装箱toy(斜率优化)
题意
求将一个长为\(n\)的序列(每个数为\(c_i\))分为很多段,每段(\(i\)~\(j\))的花费是\(\displaystyle (j-i+\sum_{k=i}^{j}c_k-L)^2\),求最小的花费。
(\(n<=50000\))
题解
容易看出\(dp\)式子如下
\[dp[i]=min\{dp[j]+(sum[i]-sum[j]+i-(j+1)-L)^2\} \quad (j < i)
\]
这个式子为\(O(n^2)\)的复杂度,显然过不去,我们进行一下斜率优化就能优化一维枚举决策点的复杂度,变成\(O(n)\)了。
接下来就需要拆式子,右边有六项,十分的难拆,但我们可以将与\(j\)有关和与\(j\)无关的分开,所以我们可以将这个式子进行一个简单的分割。
即让\(a[i]\)为\(sum[i]+i-1-L\),\(b[j]\)为\(sum[j]+j\)。
原式就化为了$$dp[i]=min{dp[j]+(a[i]-b[j])^2} \quad (j < i)$$
\[dp[i]=min\{dp[j]+a[i]^2-2*a[i]*b[j]+b[j]^2\}
\]
当\(j\)比\(k\)更优的时候满足\((k<j)\)
\[dp[j]-2*a[i]*b[j]+b[j]^2<dp[k]-2*a[i]*b[k]+b[k]^2
\]
\[(dp[j]+b[j]^2)-(dp[k]+b[k]^2)<2*a[i]*(b[j]-b[k])
\]
$$\frac{(dp[j]+b[j]2)-(dp[k]+b[k]2)}{(b[j]-b[k])} < 2*a[i]$$
然后\(a[i]\)显然满足单调递增。可以用单调队列去维护。
\(q[Head+1]\)比\(q[Head]\)要优,弹出队首。
然后弹出队尾的时有些麻烦,但结论还是很简单的,如果\(k(q[Tail-1],q[Tail])\)斜率大于\(k(q[Tail],i)\)就可以弹出队尾。(这个可以简单证明一下)
最后就直接可以每次将队首作为决策转移点去转移了。
代码
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), _end_ = (int)(r); i <= _end_; ++i)
#define Fordown(i, r, l) for(register int i = (r), _end_ = (int)(l); i >= _end_; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;
inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar() ) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar() ) x = (x<<1) + (x<<3) + (ch ^ '0');
return x * fh;
}
void File() {
#ifdef zjp_shadow
freopen ("P1010.in", "r", stdin);
freopen ("P1010.out", "w", stdout);
#endif
}
typedef long long ll;
const int N = 50100;
int n;
ll sum[N], dp[N], L;
ll a[N], b[N];
#define pow2(x) ((x) * (x))
inline ll Dp(int i, int j) {
return dp[j] + pow2(a[i] - b[j]);
}
inline ll Up(int j, int k) {
return (dp[j] + pow2(b[j]) ) - (dp[k] + pow2(b[k]) );
}
inline ll Down(int j, int k) {
return b[j] - b[k];
}
int q[N];
int Head, Tail = 1;
int main () {
File() ;
n = read();
L = read();
a[0] = - 1 - L;
For (i, 1, n) {
sum[i] = sum[i - 1] + read();
b[i] = sum[i] + i;
a[i] = b[i] - 1 - L;
}
Set(dp, 0x3f); dp[0] = 0;
For (i, 1, n) {
while (Head + 1 < Tail && Up(q[Head + 1], q[Head]) <= 2 * a[i] * Down(q[Head + 1], q[Head]) ) ++ Head;
dp[i] = Dp(i, q[Head]);
while (Head + 1 < Tail && Up(i, q[Tail - 1]) * Down(q[Tail - 1], q[Tail - 2]) <= Up(q[Tail - 1], q[Tail - 2]) * Down(i, q[Tail - 1]) ) -- Tail;
q[Tail ++] = i;
}
printf ("%lld\n", dp[n]);
return 0;
}