P3195 [HNOI2008]玩具装箱

Posted Jozky86

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P3195 [HNOI2008]玩具装箱相关的知识,希望对你有一定的参考价值。

P3195 [HNOI2008]玩具装箱

题意:

n件玩具,第i件玩具经过压缩后的一维长度为 C i C_i Ci,现在把玩具装入一维容器中,要求:

  1. 在一个一维容器中的玩具编号是连续的
  2. 如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物。

制作容器的费用与容器的长度有关。如果容器长度为x,其制作费用为 ( x − L ) 2 (x-L)^2 (xL)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])+(ij1)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]22a[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=2a[i]b[j]+dp[i]a[i]2
a[i]=sum[i]+i,而sum是可以算出来的,也就是说a[]是可以预处理出来的,我们可以当作是常数
那么这个式子我们就可以认为:
是一条斜率为 2 ∗ a [ i ] 2*a[i] 2a[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] 2a[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)>2a[i]的点
slope表示斜率

再看这个图:如果维护凸包,一定时弹掉B,为什么?如果一条直线斜率大于GC的直线从下面平移上来,一定会到C这里停下。如果是一条斜率小于GC大于FG的,显然会在G停下来,B注定要被舍弃,这就是为什么要维护凸包。

因此我们用单调队列来优化这个凸包:

设队首为head,队尾为tail:

  1. 对队首
    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)<2a[i])  head++
    当斜率小于2a[i]时,弹出队首
  2. 此时队首的点即为最优,根据它计算出dp[i]
  3. 对队尾:
    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(Ptail1,Ptail)>slope(Ptail,Pi))  tail
  4. 在队尾插入 P i P_i Pi

初 始 化 时 要 加 入 单 调 队 列 的 点 为 P 0 而 不 是 P 1 初始化时要加入单调队列的点为P_0而不是P_1 P0P1
在这里我们总结一下,单调队列斜率优化的步骤:
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]玩具装箱的主要内容,如果未能解决你的问题,请参考以下文章

P3195 [HNOI2008]玩具装箱TOY DP+优化

洛谷P3195 [HNOI2008]玩具装箱TOY(单调队列优化DP)

BZOJ1010HNOI2008玩具装箱

bzoj1010HNOI2008玩具装箱 toy

[HNOI2008]玩具装箱toy

BZOJ 1010 HNOI2008 玩具装箱