[HNOI2008] 玩具装箱

前言

终于进入新专题了家人们

列举一下还要补的专题

  1. 矩阵快速幂
  2. 组合数学
  3. sos dp

不过先不管了, 除了复习, 大部分时候总要向前看的嘛

考试的时候带着耳塞, 不带感觉好吵啊, 但是一直带着会神经衰弱的, 所以只能尝试集中注意力

思路

首先转化题意

给定 n 条线段, 第 i 条线段的长度为 Ci , 我们要将这些线段分开, 其中一个分段 [L,R] 的花费为 cost=(RL+i=LRCiW)2 , 求最小的花费之和

容易想到 dp , 令 dpi 表示考虑到第 i 条线段的最优花费
容易找到转移

dpi=minj[0,i]dpj+(ij1+preiprejW)2=minj[0,i]dpj+[(prei+i)(prej+j)(W+1)]2k=(W+1),vi=prei+i=minj[0,i]dpj+(vivjk)2=minj[0,i]dpj+(vivj)2+k22k(vivj)=k2+minj[0,i]dpj+(vi2+vj22vivj)2kvi+2kvj=k2+minj[0,i]dpj+(vi22kvi+k2)+(vj2+2kvj+k2)2vivj=k2+minj[0,i]dpj+(vik)2+(vj+k)22vivj=vi22kvi+minj[0,i]dpj+(vj+k)22vivj

考虑最终的柿子

dpi=vi22kvi+minj[0,i]dpj+(vj+k)22vivj

考虑

minj[0,i]dpj+(vj+k)22vivj

你发现 vivj 这样的形式, 考虑斜率优化
先不管 vi22kvi , 我们写出柿子

dpi=minj[0,i]dpj+(vj+k)22vivj

拆掉 min

dpi=dpj+(vj+k)22vivj

dpj+(vj+k)2=2vivjdpi

套路的, 令 x=vj,y=dpj+(vj+k)2,k=2vi,b=dpi
柿子转化为

y=kx+b

考虑其性质满足 k,x 单调递增, 我们简单的运用斜率优化, 使得 b 最大

dpimin=bmax+vi22kvi

考虑实现,
由于 k,x 的单增, 我们考虑使用单调队列维护上凸包
单调队列维护点即可

实现

框架#

直接按照上述内容实现即可

代码#

#include <bits/stdc++.h>
#define int long long
const int MAXN = 5e4 + 20;

int n, K;
int C[MAXN];

int dp[MAXN]; // 状态
int pre[MAXN], v[MAXN]; // 前缀和及其导出
namespace slope {
    int X(int a) { return v[a]; }
    int Y(int a) { return dp[a] + (v[a] + K) * (v[a] + K); }
    int slope_up(int a, int b) { return Y(b) - Y(a); }
    int slope_down(int a, int b) { return X(b) - X(a); }
} using namespace slope;

/*维护下凸包的单调队列*/
struct monoqueue {
    std::deque<int> q;
    
    bool checktail(int x) {
        return slope_up(x, q.back()) * slope_down(q.back(), q.at(q.size() - 2)) <= slope_up(q.back(), q.at(q.size() - 2)) * slope_down(x, q.back());
    }

    /*找到当前斜率下最优的情况*/
    int get(int k) {
        /*弹出不满足下凸包性质的点*/ while (q.size() >= 2 && slope_up(q.front(), q.at(1)) <= k * slope_down(q.front(), q.at(1))) q.pop_front();
        return q.front();
    }

    /*插入当前点*/
    void insert(int x) {
        /*弹出不满足下凸包性质的点*/ while (q.size() >= 2 && checktail(x)) q.pop_back();
        q.push_back(x);
    }
} mq;

signed main()
{
    scanf("%lld %lld", &n, &K); K++;
    for (int i = 1; i <= n; i++) scanf("%lld", &C[i]), pre[i] = pre[i - 1] + C[i], v[i] = pre[i] + i;

    /*初始化*/
    dp[0] = 0;
    mq.insert(0);

    for (int i = 1; i <= n; i++) {
        int j = mq.get(2 * v[i]);
        dp[i] = dp[j] + (i - j + pre[i] - pre[j] - K) * (i - j + pre[i] - pre[j] - K);
        mq.insert(i);
    }
    printf("%lld", dp[n]);

    return 0;
}

总结

常见的 dp 状态设计

优化不来可以考虑继续研究柿子

常见的斜率优化, 拆柿子能力史诗级提升

posted @   Yorg  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示