专栏文章

Slope Trick 优化 DP

P2748题解参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@mipoyp7n
此快照首次捕获于
2025/12/03 15:34
3 个月前
此快照最后确认于
2025/12/03 15:34
3 个月前
查看原文
f(i,v)f(i,v) 为处理完前 ii 堆泥土且结余 vv 单位泥土的最小成本。问题的答案就是 f(n,0)f(n,0)
可以写出状态转移方程:
f(i,v)=minwf(i1,w)+wZ+h((biai)(wv)),f(i,v) = \min_w f(i-1,w)+|w|Z+h((b_i-a_i)-(w-v)),
其中,
h(δ)=max{δ,0}X+max{δ,0}Yh(\delta) = \max\{\delta,0\}X+\max\{-\delta,0\}Y
是用于补足第 ii 堆泥土的亏空(或盈余)(即 (biai)(wv)(b_i-a_i)-(w-v))的成本,而 wZ|w|Z 是将前一次剩余(或缺少)的土方搬到当前位置的新增成本。
方程只涉及分段线性的凸函数,因此考虑使用 slope trick。
f(i1,w)f(i-1,w) 的基础上:
  • wZ|w|Z 相当于在原点左侧的斜率都减去 ZZ,在原点右侧的斜率都加上 ZZ
  • 不考虑 (biai)(b_i-a_i) 项,后面就是 h(vw)h(v-w),将它与 f(i1,w)+wZf(i-1,w)+|w|Z 的和取 ww 的最小值,相当于做 Minkowski 和,结果就是将 f(i1,w)+wZf(i-1,w)+|w|Z 的所有斜率(即差分)与 Y-Y 取最大值,与 XX 取最小值;
  • 最后,在得到的函数的基础上再加上 biaib_i-a_i 项相当于将它向左平移 biaib_i-a_i 的单位。
基于这些分析,只需要维护原点左右两侧离原点最近的几个斜率的值即可,事实上只需要两个栈。整体的操作用懒标记,左右移动只需要将栈顶的元素移动一下。
因为更远处的斜率一定为 Y-YXX,所以不必记录,当左侧或右侧的栈空了的时候直接补相应的值即可。
最后的答案是 00 处的取值,所以在左右移动时需要更新答案。
复杂度是 O(nmax{a,b})O(n\max\{a,b\}) 的。
核心代码如下:
CPP
int main() {
    int n;
    long long x, y, z;
    std::cin >> n >> x >> y >> z;
    std::stack<long long> neg, pos;
    long long lt = 0, rt = 0;
    long long res = 0;
    for (; n; --n) {
        int a, b;
        std::cin >> a >> b;
        lt -= z;
        rt += z;
        for (; b < a; ++b) {
            auto l = -y;
            if (!neg.empty()) {
                l = std::max(l, neg.top() + lt);
                neg.pop();
            }
            pos.emplace(l - rt);
            res -= l;
        }
        for (; b > a; --b) {
            auto r = x;
            if (!pos.empty()) {
                r = std::min(r, pos.top() + rt);
                pos.pop();
            }
            neg.emplace(r - lt);
            res += r;
        }
    }
    std::cout << res;
    return 0;
}

评论

1 条评论,欢迎与作者交流。

正在加载评论...