HNOI 2008 玩具装箱toy(DP + 斜率优化)

题意:

有一堆玩具可以压缩成 Ci 长度,放在容器中,同时一个容器内玩具的编号必须是连续的,每个容器所需要的费用题目中给了,求最小费用。

思路:

1. 网上推导的代码啰里啰嗦的,完全没有理解清楚不变量 i 的真谛,从而对于平方的展开添加了很多不必要的麻烦。

2. dp[i] 表示拿到第 i 件玩具时,所需要的最小费用。当 i 和前面的 x 件连着时,才有有递推式:dp[i] = dp[j] + (i-j-1 + Ci-Cj + L)2;

3. 对平方里面的因式进行必要的化简:S[i] = i + Ci; 于是 dp[i] = dp[j] + (Si - Sj + L - 1)2;

4. 再进行精简,令 m = Si + L - 1; dp[i] = dp[j] + (m - Sj)= dp[j] + m2 - 2*m*Sj + Sj2;

5. 利用斜率优化,此时有 X = Sj, Y = dp[j] + Sj2, a = 2*m;  a 满足递增的特性,求 dp[i] 最小。根据下凸函数的特性,利用队列对其进行优化即可,O(N);

6. 要注意的一个边界是 :当 i 固定, j 可能取 0 时使结果最优,所以对于 0 要提前入队,这样得到总体最优解。

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
 
#define LL long long int
 
const int MAXN = 50010;
 
int deq[MAXN];
LL C[MAXN], S[MAXN], dp[MAXN];
 
inline double slope(int i, int j)
{
    return 1.0 * (dp[i] + S[i] * S[i] - dp[j] - S[j] * S[j]) / (S[i] - S[j]);
}
 
int main()
{
    int N, L;
    while (scanf("%d %d", &N, &L) != EOF)
    {
        C[0] = S[0] = 0;
        for (int i = 1; i <= N; ++i)
        {
            scanf("%lld", &C[i]);
            C[i] += C[i-1], S[i] = i + C[i];
        }
 
        int s = 0, e = 0;
        dp[0] = deq[s] = 0;
        for (int i = 1; i <= N; ++i)
        {
            LL m = S[i] - L - 1;
 
            while (s < e && slope(deq[s+1], deq[s]) <= 2 * m)
                ++s;
 
            int j = deq[s];
            dp[i] = dp[j] + (m - S[j]) * (m - S[j]);
 
            while (s < e && slope(deq[e], deq[e-1]) >= slope(i, deq[e]))
                --e;
            deq[++e] = i;
        }
        printf("%lld\n", dp[N]);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/kedebug/p/2941359.html