其他分享
首页 > 其他分享> > [HNOI2008]玩具装箱

[HNOI2008]玩具装箱

作者:互联网

「HNOI2008」玩具装箱

题目大意

有 \(n\) 个玩具,第 \(i\) 个玩具的价值为 \(c_i\) 。这 \(n\) 个玩具排成一排,要求将这些玩具分成若干段,对于一段 \([l,r]\) ,其代价为 \((r-l+\Sigma_{i=l}^{r}c_i-L)^2\) 。其中 \(L\) 是一个常量,求分段最小代价。\((1 \leq n \leq 5 \times 10^4,1 \leq L,c_i \leq 10^7)\)

题解(斜率优化)

比较容易想到的是直接dp做:

设 \(f_i\) 为前i个物品分成若干段的最小代价。

那么状态转移方程为:

\[f_i = \min_{j<i}\{f_j + ((i - j - 1) + pre_i - pre_j - L)^2\} = \min_{j<i}\{f_j + (pre_i + i - pre_j - j - 1 - L)^2\} \]

那么这样的做法时间复杂度是 \((n^2)\) 的,是过不了的。

考虑优化:

对于上述状态转移方程,我们在进行变换:

为方便理解,令 \(a_i\) 等于 \(pre_i+i\) ,\(L'=L+1\) 。原式子等于\(f_i = \min_{j<i}\{f_j + (a_i - a_j - L')^2\}\)

将与 \(j\) 有关的放一边,我们能得到:

\[f_i-(a_i-L')^2 = \min_{j<i}\{f_j + a_j^2 -2a_j(a_i-L')\} \]

我们要找到一个 \(j\) 使得 \(f_i\) 最小,我们便设与 \(j\) 相关的数设为变量。通过上面的式子我们可以发现,若我们将一次函数的斜截式 \(y=kx+b\) 代入其中,也就是 \(b=y-kx\) ,将 \(i\) 相关的令作常量,与 \(j\) 相关的令作变量,即:

\[\begin{aligned} x_j&=a_j\\ y_j&=f_j+a_j^2\\ k_i&=2(a_i-L')\\ b_i&=f_i-(a_i-L')^2 \end{aligned} \]

上述转移方程可以写成 \(b_i=\min_{j<i}\{y_j-k_ix_j\}\) ,首先 \(k_i\) 是一个常量,我们把 \(\{x_j,y_j\}\) 看成平面直角坐标系上的点,这样我们就把原问题转化成了选择一个合适的点 \(j\ (1 \leq j < i)\) ,使得截距 \(b_i\) 最小。

斜率优化

如上图所示,我们将图中的直线向上平移,第一个接触到的点B显然是使得截距最小的点,且我们会发现能使得截距最小的点一定是下凸点,以点B为例,前一个点A,和后一个点B构成的斜率满足 \(k_{AB} < k_{BC}\) ,所以点B是下凸点,同时点B是合适点的另一条件是 \(k_{AB} \leq k_i < k_{BC}\) ,且本题中,\(k_i\) 是递增的,所以,我们可以用一个单调队列维护下凸点(即能使得斜率递增的点),步骤如下:

  1. 首先是在队首 \(head\) ,判断是否 \(k_{head,head+1} > k_i\) ,如果不是说明点 \(head\) 并不是合适点,删除即可,直到满足 \(k_{head,head+1} > k_i\) 。
  2. 之后根据合适点更新 \(f_i\)
  3. 加入新的点进入队尾 \(tail\) 前,先判断 \(k_{tail-1,tail} < k_{tail, new}\) ,如果不成立,则队尾的点不是下凸点,删除并且找到满足的即可

具体实现看下列代码

#include <algorithm>
#include <cstdio>
using ll = long long;

const int N = 5e4 + 5;

ll dp[N], pre[N], q[N], n, L, head, tail;

ll a(int i){
    return pre[i] + i;
}

ll Y(int i){
    return dp[i] + a(i) * a(i);
}

double K(int i, int j){
    return 1. * (Y(i) - Y(j)) / (a(i) - a(j));
}


int main(void){
    scanf("%lld%lld", &n, &L);
    L++;
    for(int i = 1; i <= n; ++i){
        int a;
        scanf("%d", &a);
        pre[i] = pre[i - 1] + a;
    }
    head = tail = 1;
    for(int i = 1; i <= n; ++i){
        while(head < tail && K(q[head], q[head + 1]) < 2 * (a(i) - L))head++;
        dp[i] = dp[q[head]] + (a(i) - a(q[head]) - L) * (a(i) - a(q[head]) - L);
        while(head < tail && K(q[tail], q[tail - 1]) > K(i, q[tail]))tail--;
        q[++tail] = i;
    }
    printf("%lld", dp[n]);
    return 0;
}

标签:head,int,ll,玩具,leq,tail,HNOI2008,装箱
来源: https://www.cnblogs.com/yanyeting/p/16553476.html