ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[HNOI2008]玩具装箱

2022-08-05 10:36:46  阅读:122  来源: 互联网

标签:head int ll 玩具 leq tail 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有