P3195 [HNOI2008]玩具装箱TOY
题意
-
有
n
个物品,每个物品长度c[i]
-
将物品分为连续的几组,每组内部每个物品用空间
1
隔开一组物品的长度为 ∑ i i < = j c [ i ] + j − i \sum_{i}^{i<=j}c[i] + j-i ∑ii<=jc[i]+j−i
-
每组物品的价值为 ( 长 度 − L ) 2 (长度-L)^2 (长度−L)2,
L
为常数
思路
状态
dp[i]
:表示加入i
后,最优分组的代价
状态转移方程
f [ i ] = m i n ( s u m [ i ] − s u m [ j ] − L ) 2 + f [ j ] f[i] = min(sum[i] - sum[j] - L) ^ 2 + f[j] f[i]=min(sum[i]−sum[j]−L)2+f[j]
斜率优化
我们发现sum[j]
有个关于i
的系数,所以我们尝试一下能不能斜率优化
f [ i ] = m i n ( s u m [ i ] − s u m [ j ] − L ) 2 + f [ j ] f[i] = min(sum[i] - sum[j] - L) ^ 2 + f[j] f[i]=min(sum[i]−sum[j]−L)2+f[j]
f
[
i
]
=
s
u
m
[
i
]
2
+
(
s
u
m
[
j
]
+
L
)
2
−
2
∗
s
u
m
[
i
]
∗
(
s
u
m
[
j
]
+
L
)
+
f
[
j
]
f[i] = sum[i] ^ 2 + (sum[j] + L) ^ 2 - 2 * sum[i] * (sum[j] + L) + f[j]
f[i]=sum[i]2+(sum[j]+L)2−2∗sum[i]∗(sum[j]+L)+f[j]
f
[
j
]
+
(
s
u
m
[
j
]
+
L
)
2
=
2
∗
s
u
m
[
i
]
∗
(
s
u
m
[
j
]
+
L
)
+
f
[
i
]
−
s
u
m
[
i
]
2
f[j] + (sum[j] + L) ^ 2 = 2 * sum[i] * (sum[j] + L) + f[i] - sum[i] ^ 2
f[j]+(sum[j]+L)2=2∗sum[i]∗(sum[j]+L)+f[i]−sum[i]2
x
:
s
u
m
[
j
]
+
L
x: sum[j] + L
x:sum[j]+L,
y
:
f
[
j
]
+
(
s
u
m
[
j
]
+
L
)
2
y : f[j] + (sum[j] + L) ^ 2
y:f[j]+(sum[j]+L)2 只与j
有关;
k
:
2
∗
s
u
m
[
i
]
k: 2 * sum[i]
k:2∗sum[i] 只与i
有关,且与dp[i]
无关;
b
:
f
[
i
]
−
s
u
m
[
i
]
2
b: f[i] - sum[i] ^ 2
b:f[i]−sum[i]2与dp[i]
直接挂钩
现在,我们按照上述的 x
和 y
建立一个平面直角坐标系。则我们需要最小化 f[i]
,就是最小化该函数的截距(截距:就是
y
=
k
x
+
b
y=kx+b
y=kx+b 中的 b
)。
由于
k
=
2
∗
s
[
i
]
k=2*s[i]
k=2∗s[i]的值不随 j
的变化而变化,因此该函数图像的斜率一定。这时我们可以把选择状态的过程想象为一条斜率一定的直线在坐标系里向上平移的过程。而因为每个不同的 j
会带来不同的 x
和 y
的取值,因此每个决策点都对应了平面直角坐标系里的一个点。为了使我们这条函数图像的截距最小,我们在向上平移的过程中一定先选第一个碰到的点。
单调队列维护凸壳
在本题中,有两个隐藏的条件:
- 横坐标
x
,即 ( s [ j ] + L ) (s[j]+L) (s[j]+L)递增。 - 相邻两点的线段斜率,即 2 ∗ s [ i ] 2*s[i] 2∗s[i]也是递增的。
因此我们可以用一个单调队列来维护前文所述的这样一个“凸壳”。对于每个状态变量 i
:
- 检查队头的两个决策变量
q[l]
和q[l+1]
,若他们的斜率 Y ( q [ l + 1 ] ) − Y ( q [ l ] ) X ( q [ l + 1 ] ) − X ( q [ l ] ) < = k \frac{Y(q[l+1])-Y(q[l])}{X(q[l+1])-X(q[l])}<=k X(q[l+1])−X(q[l])Y(q[l+1])−Y(q[l])<=k(这里k
、x
、y
就是前面改写状态转移方程得到的),则将队头q[l]
弹出。 - 直接取出队头
j
=
q
[
l
]
j=q[l]
j=q[l]为最优决策,计算出
f[i]
。 - 把新决策
i
从队尾插入。在插入之前,若三个决策点q[r-1]
,q[r]
,i
不满足斜率单调递增(不满足下凸性,即q[r]
是无用决策),则直接从队尾让q[r]
出队,继续检查新的队尾。
由于每个元素最多入队、出队一次,因此整个算法的时间复杂度为 O(n)
代码
const int maxn = 50005;
typedef long long LL;
typedef pair<LL, LL> pll;
LL sum[maxn];
pll q[maxn];
int main()
{
int n;
LL L;
scanf("%d%lld", &n, &L);
for (int i = 1; i <= n; i++)
scanf("%lld", &sum[i]);
for (int i = 1; i <= n; i++)
sum[i] += sum[i - 1];
//f[i]=min(sum[i]-sum[j]+i-(j+1)-L)^2+f[j];
L++;
for (int i = 1; i <= n; i++)
sum[i] += i;
// k: 2 * sum[i] 只与i有关,且与dp[i] 无关;
// b: f[i] - sum[i] ^ 2 与dp[i] 直接挂钩
int l = 1, r = 1;
register LL x, y, k, b;
q[1] = make_pair(L, L * L);
for (int i = 1; i <= n; i++) {
k = 2 * sum[i];
//(y2-y1)/(x2-x1) <= k
while (l < r && (q[l + 1].second - q[l].second) * 1.0 / (q[l + 1].first - q[l].first) <= k)
l++;
b = q[l].second - k * q[l].first;
x = sum[i] + L;
y = b + sum[i] * sum[i] + x * x;
//(y3-y1)/(x3-x1) < (y2-y1)/(x2-x1)
while (l < r && (y - q[r - 1].second) * 1.0 / (x - q[r - 1].first) <= (q[r].second - q[r - 1].second) * 1.0 / (q[r].first - q[r - 1].first))
r--;
q[++r] = make_pair(x, y);
}
printf("%lld\n", q[r].second - q[r].first * q[r].first);
return 0;
}