P3195 [HNOI2008]玩具装箱
Posted Jozky86
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P3195 [HNOI2008]玩具装箱相关的知识,希望对你有一定的参考价值。
题意:
n件玩具,第i件玩具经过压缩后的一维长度为 C i C_i Ci,现在把玩具装入一维容器中,要求:
- 在一个一维容器中的玩具编号是连续的
- 如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物。
制作容器的费用与容器的长度有关。如果容器长度为x,其制作费用为
(
x
−
L
)
2
(x-L)^2
(x−L)2.
问所有容器的总费用最小是多少
题解:
斜率优化dp入门题
前置知识:
需要会单调队列优化
当dp方程为dp[i]=a[i]+b[i]时,这个方程是
O
(
n
2
)
O(n^2)
O(n2)的
此时可以用单调队列将其优化为O(n)
当dp方程为dp[i]=a[i]*b[j]+c[i]+d[j]时,因为存在a[i]*b[j]这个即有i又有j的项,此时单调队列不好用,就需要使用斜率优化
回到本题:
这个题的转移方程很好写:
sum[i]表示前i件玩具的长度和
dp[i]表示把前i件玩具装进容器所需要的最小费用
d
p
[
i
]
=
m
i
n
(
d
p
[
j
]
+
(
(
s
u
m
[
i
]
−
s
u
m
[
j
]
)
+
(
i
−
j
−
1
)
−
L
)
2
)
(
j
<
i
)
dp[i]=min(dp[j]+((sum[i]-sum[j])+(i-j-1)-L)^2)(j<i)
dp[i]=min(dp[j]+((sum[i]−sum[j])+(i−j−1)−L)2)(j<i)
不过转移显然是
O
(
n
2
)
O(n^2)
O(n2)的,因此需要进行优化:
我们设a[i]=sum[i]+i,b[j]=sum[j]+j+L+1(就是将i和j的式子分开,a只与i有关,b只与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]+(a[i]−b[j])2
展开有:
d
p
[
i
]
=
d
p
[
j
]
+
a
[
i
]
2
−
2
∗
a
[
i
]
∗
b
[
j
]
+
b
[
j
]
2
dp[i]=dp[j]+a[i]^2-2*a[i]*b[j]+b[j]^2
dp[i]=dp[j]+a[i]2−2∗a[i]∗b[j]+b[j]2
移项有:
d
p
[
j
]
+
b
[
j
]
2
=
2
∗
a
[
i
]
∗
b
[
j
]
+
d
p
[
i
]
−
a
[
i
]
2
dp[j]+b[j]^2=2*a[i]*b[j]+dp[i]-a[i]^2
dp[j]+b[j]2=2∗a[i]∗b[j]+dp[i]−a[i]2
a[i]=sum[i]+i,而sum是可以算出来的,也就是说a[]是可以预处理出来的,我们可以当作是常数
那么这个式子我们就可以认为:
是一条斜率为
2
∗
a
[
i
]
2*a[i]
2∗a[i]的直线,解决为
d
p
[
i
]
−
a
[
i
]
2
dp[i]-a[i]^2
dp[i]−a[i]2
此时dp[i]的含义就是:当上述直线过点
(
b
[
j
]
,
d
p
[
j
]
+
b
[
j
]
2
)
(b[j],dp[j]+b[j]^2)
(b[j],dp[j]+b[j]2)时,直线在y轴的截距为
d
p
[
i
]
+
a
[
i
]
2
dp[i]+a[i]^2
dp[i]+a[i]2
现在我们要求的就是这个截距的最小值
直线的斜率为
2
∗
a
[
i
]
2*a[i]
2∗a[i]是递增的,由此可以联想到凸包,凸包中相邻两点的斜率是单调递增的
由下面的图像可知,满足条件的最优的点
P
j
P_j
Pj为第一个
s
l
o
p
e
(
P
j
,
P
j
+
1
)
>
2
∗
a
[
i
]
slope(P_j,P_{j+1})>2*a[i]
slope(Pj,Pj+1)>2∗a[i]的点
slope表示斜率
再看这个图:如果维护凸包,一定时弹掉B,为什么?如果一条直线斜率大于GC的直线从下面平移上来,一定会到C这里停下。如果是一条斜率小于GC大于FG的,显然会在G停下来,B注定要被舍弃,这就是为什么要维护凸包。
因此我们用单调队列来优化这个凸包:
设队首为head,队尾为tail:
- 对队首
w h i l e ( s l o p e ( P h e a d , P h e a d + 1 ) < 2 ∗ a [ i ] ) h e a d + + while(slope(P_{head},P_{head+1})<2*a[i])~~head++ while(slope(Phead,Phead+1)<2∗a[i]) head++
当斜率小于2a[i]时,弹出队首 - 此时队首的点即为最优,根据它计算出dp[i]
- 对队尾:
w h i l e ( s l o p e ( P t a i l − 1 , P t a i l ) > s l o p e ( P t a i l , P i ) ) t a i l − − while(slope(P_{tail-1},P_{tail})>slope(P_{tail},P_i))~~tail-- while(slope(Ptail−1,Ptail)>slope(Ptail,Pi)) tail−− - 在队尾插入 P i P_i Pi
初
始
化
时
要
加
入
单
调
队
列
的
点
为
P
0
而
不
是
P
1
初始化时要加入单调队列的点为P_0而不是P_1
初始化时要加入单调队列的点为P0而不是P1
在这里我们总结一下,单调队列斜率优化的步骤:
1.弹队头,就是最左边的点.
2.放直线,算答案,得到当前状态的答案,得到新的待加入的点.
3.弹队尾,把插入新点之后不合法的点弹掉.最后加入新点就好了.
代码:
#include <cmath>
#include <cstdio>
#include <iostream>
#define maxn 50005
#define ll long long
using namespace std;
int q[maxn];
double A[maxn], B[maxn], dp[maxn], sum[maxn];
double X(int x)
{
return B[x];
}
double Y(int x)
{
return dp[x] + B[x] * B[x];
}
double slope(int a, int b)
{
return (Y(a) - Y(b)) / (X(a) - X(b));
}
int main()
{
int n, l;
cin >> n >> l;
for (int i= 1; i <= n; i++)
scanf("%lf", &sum[i]);
for (int i= 1; i <= n; i++) {
sum[i]+= sum[i - 1];
A[i]= sum[i] + i;
B[i]= sum[i] + i + l + 1;
}
B[0]= l + 1; //B[0]=sum[0]+0+l+1=l+1
int tail= 1, head= 1;
for (int i= 1; i <= n; i++) {
while (head < tail && slope(q[head], q[head + 1]) < 2 * A[i]) //如果队首不符合要求弹出
head++;
int j= q[head];
dp[i]= dp[j] + (A[i] - B[j]) * (A[i] - B[j]); //进行状态转移
//舍去不合格点,加入新点
while (head < tail && slope(i, q[tail - 1]) < slope(q[tail - 1], q[tail]))
tail--;
q[&以上是关于P3195 [HNOI2008]玩具装箱的主要内容,如果未能解决你的问题,请参考以下文章