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

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

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

题意

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

1 ≤ n ≤ 5 × 1 0 4 1 \leq n \leq 5 \times 10^4 1n5×104 1 ≤ L ≤ 1 0 7 1 \leq L \leq 10^7 1L107 1 ≤ C i ≤ 1 0 7 1 \leq C_i \leq 10^7 1Ci107

f i f_i fi 表示前 i i i 个物品分成若干段的最小代价,则
f i = min ⁡ j < i { f j + ( S i + i − S j − j − L − 1 ) 2 } f_i = \min_{j < i}\left\{f_j + (S_i + i -S_j -j -L-1)^2\right\} fi=j<imin{fj+(Si+iSjjL1)2}
其中 S i S_i Si 为前 i i i 个物品的价值和,即 S i = ∑ j = 1 i c j S_i = \sum_{j=1}^{i}c_j Si=j=1icj

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

a i = S i + i ,   b i = S i + i + L + 1 a_i = S_i + i,~b_i = S_i + i + L+1 ai=Si+i, bi=Si+i+L+1 ,则
f i = min ⁡ j < i { f j + ( a i − b j ) 2 } = min ⁡ j < i { f j + a i 2 − 2 a i b j + b j 2 } \begin{aligned} f_i &= \min_{j < i}\left\{ f_j + (a_i-b_j)^2 \right\} \\&=\min_{j < i}\left\{ f_j + a_i^2 - 2a_ib_j + b_j^2\right\} \end{aligned} fi=j<imin{fj+(aibj)2}=j<imin{fj+ai22aibj+bj2}
移项得
f i − a i 2 = min ⁡ j < i { f j − 2 a i b j + b j 2 } f_i - a_i^2 = \min_{j < i}\left\{ f_j -2a_ib_j + b_j^2\right\} fiai2=j<imin{fj2aibj+bj2}
注意到右边的部分有一个 2 a i 2a_i 2ai 导致我们无法单调队列优化

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

根据斜截式方程可得
b = y − k x b=y-kx b=ykx
可知
b i = f i − a i 2 k i = 2 a i x j = b j y j = f j + b j 2 \begin{aligned} b_i&=f_i-a_i^2 \\k_i&=2a_i \\x_j&=b_j \\y_j &= f_j + b_j^2 \end{aligned} bikixjyj=fiai2=2ai=bj=fj+bj2
则转移方程可以写成如下形式
b i = min ⁡ { y j − k i x j } b_i = \min\left\{ y_j-k_ix_j\right\} bi=min{yjkixj}
( x j , y j ) (x_j,y_j) (xj,yj) 映射到平面上的点,那么问题就转化为了

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

由于这个 k i k_i ki 是严格单调递增的,

不难发现这个点一定在 ( x j , y j ) (x_j,y_j) (xj,yj) 点集形成的下凸壳上,

并且这个点 P j P_j Pj j j j 是满足直线 P j P j + 1 P_jP_{j+1} PjPj+1 的斜率 k j ′ > k i k_j^{\prime}>k_i kj>ki 最小的 j j j

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

考虑如何维护这个下凸壳

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

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

对于斜率比 k i k_i ki 小的而在下凸壳上的点,直接把它单调队列掉qwq

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

时间复杂度 O ( n ) O(n) 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;
}

转载请说明出处

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值