Slope Trick 优化 DP
设
可以写出状态转移方程:
其中,
是用于补足第
方程只涉及分段线性的凸函数,因此考虑使用 slope trick。
在
- 加
|w|Z 相当于在原点左侧的斜率都减去Z ,在原点右侧的斜率都加上Z ; - 不考虑
(b_i-a_i) 项,后面就是h(v-w) ,将它与f(i-1,w)+|w|Z 的和取w 的最小值,相当于做 Minkowski 和,结果就是将f(i-1,w)+|w|Z 的所有斜率(即差分)与-Y 取最大值,与X 取最小值; - 最后,在得到的函数的基础上再加上
b_i-a_i 项相当于将它向左平移b_i-a_i 的单位。
基于这些分析,只需要维护原点左右两侧离原点最近的几个斜率的值即可,事实上只需要两个栈。整体的操作用懒标记,左右移动只需要将栈顶的元素移动一下。
因为更远处的斜率一定为
最后的答案是
复杂度是
核心代码如下:
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;
}