洛谷P3195 [HNOI2008]玩具装箱 题解
题目链接:P3195 [HNOI2008]玩具装箱
题意:
有 n n n 个玩具,第 i i i 个玩具的价值为 c i c_i ci 。要求将这 n n n 个玩具排成一排,分成若干段。对于一段 [ l , r ] [l,r] [l,r] ,它的代价为 ( r − l + ∑ i = l r c i − L ) 2 (r-l+\sum_{i=l}^{r}c_i-L)^2 (r−l+∑i=lrci−L)2 ,其中 L L L 为一个常量,求分段的最小代价。
1 ≤ n ≤ 5 × 1 0 4 1 \leq n \leq 5 \times 10^4 1≤n≤5×104, 1 ≤ L ≤ 1 0 7 1 \leq L \leq 10^7 1≤L≤107, 1 ≤ C i ≤ 1 0 7 1 \leq C_i \leq 10^7 1≤Ci≤107
令
f
i
f_i
fi 表示前
i
i
i 个物品分成若干段的最小代价,则
f
i
=
min
j
<
i
{
f
j
+
(
S
i
+
i
−
S
j
−
j
−
L
−
1
)
2
}
f_i = \min_{j < i}\left\{f_j + (S_i + i -S_j -j -L-1)^2\right\}
fi=j<imin{fj+(Si+i−Sj−j−L−1)2}
其中
S
i
S_i
Si 为前
i
i
i 个物品的价值和,即
S
i
=
∑
j
=
1
i
c
j
S_i = \sum_{j=1}^{i}c_j
Si=∑j=1icj
直接去做是 O ( n 2 ) O(n^2) O(n2) ,考虑斜率优化
令
a
i
=
S
i
+
i
,
b
i
=
S
i
+
i
+
L
+
1
a_i = S_i + i,~b_i = S_i + i + L+1
ai=Si+i, bi=Si+i+L+1 ,则
f
i
=
min
j
<
i
{
f
j
+
(
a
i
−
b
j
)
2
}
=
min
j
<
i
{
f
j
+
a
i
2
−
2
a
i
b
j
+
b
j
2
}
\begin{aligned} f_i &= \min_{j < i}\left\{ f_j + (a_i-b_j)^2 \right\} \\&=\min_{j < i}\left\{ f_j + a_i^2 - 2a_ib_j + b_j^2\right\} \end{aligned}
fi=j<imin{fj+(ai−bj)2}=j<imin{fj+ai2−2aibj+bj2}
移项得
f
i
−
a
i
2
=
min
j
<
i
{
f
j
−
2
a
i
b
j
+
b
j
2
}
f_i - a_i^2 = \min_{j < i}\left\{ f_j -2a_ib_j + b_j^2\right\}
fi−ai2=j<imin{fj−2aibj+bj2}
注意到右边的部分有一个
2
a
i
2a_i
2ai 导致我们无法单调队列优化
但是我们可以斜率优化,也就是吧 2 a i 2a_i 2ai 看作 k k k
根据斜截式方程可得
b
=
y
−
k
x
b=y-kx
b=y−kx
可知
b
i
=
f
i
−
a
i
2
k
i
=
2
a
i
x
j
=
b
j
y
j
=
f
j
+
b
j
2
\begin{aligned} b_i&=f_i-a_i^2 \\k_i&=2a_i \\x_j&=b_j \\y_j &= f_j + b_j^2 \end{aligned}
bikixjyj=fi−ai2=2ai=bj=fj+bj2
则转移方程可以写成如下形式
b
i
=
min
{
y
j
−
k
i
x
j
}
b_i = \min\left\{ y_j-k_ix_j\right\}
bi=min{yj−kixj}
把
(
x
j
,
y
j
)
(x_j,y_j)
(xj,yj) 映射到平面上的点,那么问题就转化为了
找到一个 1 ≤ j < i 1\le j<i 1≤j<i 最小化过点 ( x j , y j ) (x_j,y_j) (xj,yj) 且斜率为 k i k_i ki 的直线的 y y y 轴截距
由于这个 k i k_i ki 是严格单调递增的,
不难发现这个点一定在 ( x j , y j ) (x_j,y_j) (xj,yj) 点集形成的下凸壳上,
并且这个点 P j P_j Pj 的 j j j 是满足直线 P j P j + 1 P_jP_{j+1} PjPj+1 的斜率 k j ′ > k i k_j^{\prime}>k_i kj′>ki 最小的 j j j
或者说,这个点和下一个在下凸壳上的点所连产生的直线是第一个斜率比 k i k_i ki 大的
考虑如何维护这个下凸壳
刚才我们也说了 k i k_i ki 是单调递增的,所以这个下凸壳是一个“动态”的
因此我们可以用单调队列来维护,而不是单调栈
对于斜率比 k i k_i ki 小的而在下凸壳上的点,直接把它单调队列掉qwq
然后每次加点就和二维凸包那个差不多,这里不细讲了
时间复杂度 O ( n ) O(n) O(n)
代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(5e4+15)
int n,L;
double sum[N],dp[N];
int st,en,q[N];
double a(int i){return sum[i]+i;}
double b(int i){return a(i)+L+1;}
double X(int i){return b(i);}
double Y(int i){return dp[i]+b(i)*b(i);}
double slope(int i,int j){return (Y(i)-Y(j))/(X(i)-X(j));}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n >> L;
for(int i=1; i<=n; i++)
{
cin >> sum[i];
sum[i]+=sum[i-1];
}
st=en=1;
for(int i=1; i<=n; i++)
{
while(st<en&&slope(q[st],q[st+1])<2*a(i))++st;
dp[i]=dp[q[st]]+(a(i)-b(q[st]))*(a(i)-b(q[st]));
while(st<en&&slope(i,q[en-1])<slope(q[en-1],q[en]))--en;
q[++en]=i;
}
cout << (int)dp[n] << '\n';
return 0;
}
转载请说明出处