嘘~ 正在从服务器偷取页面 . . .

洛谷P3195 [HNOI2008]玩具装箱 题解


洛谷P3195 [HNOI2008]玩具装箱 题解

题目链接:P3195 [HNOI2008]玩具装箱

题意

有 $n$ 个玩具,第 $i$ 个玩具的价值为 $c_i$ 。要求将这 $n$ 个玩具排成一排,分成若干段。对于一段 $[l,r]$ ,它的代价为 $(r-l+\sum_{i=l}^{r}c_i-L)^2$ ,其中 $L$ 为一个常量,求分段的最小代价。

$1 \leq n \leq 5 \times 10^4$,$1 \leq L \leq 10^7$,$1 \leq C_i \leq 10^7$

upd. 新增斜率优化新的理解。

令 $f_i$ 表示前 $i$ 个物品分成若干段的最小代价,则

其中 $S_i$ 为前 $i$ 个物品的价值和,即 $S_i = \sum_{j=1}^{i}c_j$

直接去做是 $O(n^2)$ ,考虑斜率优化

令 $a_i = S_i + i,~b_i = S_i + i + L+1$ ,则

移项得

注意到右边的部分有一个 $2a_i$ 导致我们无法单调队列优化

但是我们可以斜率优化,也就是吧 $2a_i$ 看作 $k$

根据斜截式方程可得

可知

则转移方程可以写成如下形式

把 $(x_j,y_j)$ 映射到平面上的点,那么问题就转化为了

找到一个 $1\le j<i$ 最小化过点 $(x_j,y_j)$ 且斜率为 $k_i$ 的直线的 $y$ 轴截距

由于这个 $k_i$ 是严格单调递增的,

不难发现这个点一定在 $(x_j,y_j)$ 点集形成的下凸壳上,

并且这个点 $P_j$ 的 $j$ 是满足直线 $P_jP_{j+1}$ 的斜率 $k_j^{\prime}>k_i$ 最小的 $j$

或者说,这个点和下一个在下凸壳上的点所连产生的直线是第一个斜率比 $k_i$ 大的

考虑如何维护这个下凸壳

刚才我们也说了 $k_i$ 是单调递增的,所以这个下凸壳是一个“动态”的

当凸壳上的某个点 $u$ 和下一个点 $v$ 所成的直线斜率小于 $k_i$ 时,

$u$ 就没用了,该被踢掉了

因此我们可以用单调队列来维护,而不是单调栈

然后每次加点就和二维凸包那个差不多,这里不细讲了(


对于斜率优化,还有另一种理解

首先刚刚的柿子还是要用到的

我们不用刚刚那个

考虑一个 $j~(j>k)$ 在什么情况下比 $k$ 优

显然有

然后推推柿子

注意到 $b_i = S_i + i + L+1$ 是单调递增的

因此有 $b_j > b_k$

则柿子可以化简为

左边这个东西长的很像斜率的定义

因此我们可以维护一个点集,然后两两点去判断

但是这样肯定是不优的

仔细思考一下,可能成为答案的点一定在这个点集的下凸壳上(因为我们要最小化 $ f_j - 2a_ib_j + b_j^2$ ,所以是下凸壳)

于是我们维护一个下凸壳,依次加入点,再踢出那些不优的点


时间复杂度 $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;
}

文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录