【题解】P2748 - Landscaping P
题目大意
给定一个长度为 \(n\) 的序列 \(a\) ,现在想通过若干次操作和最小的花费将其变成序列 \(b\) ,您可以:
-
花费 \(x\) 的代价在任意一个位置加上 \(1\)
-
花费 \(y\) 的代价在任意一个位置减去 \(1\)
-
花费 \(z \times |i - j|\) 的代价从第 \(i\) 个位置移动一个单位的 “泥土” 给 第 \(j\) 个位置,也就是给 \(i - 1, j + 1\) 。
\(n \leq 200000\)
解题思路
这道题的解法分明是很妙的,很有趣的,但是蒟蒻实在有点看不懂其他的题解,所以决定自己写了一篇留着以后看。显然,这道题的难度跟弱化版本根本不是一个级别的……
首先大致考虑一下思考的方向。一个显而易见的 \(dp\) 做法是,将 \(a\) 序列处理成 由连续 \(a_i\) 个 \(i\) 按 \(i\) 从小到大组成的序列,然后直接进行 编辑距离 ,时间复杂度 \(O(100n ^ 2)\) ,空间复杂度 \(O(n ^ 2)\) ,原地爆炸。
还是考虑 \(dp\) 。显然时空限制只能让我们定义一维的状态。但是无论怎么定义状态,更优的方案总是需要重复更新之前的状态。也就是说,这个问题是 有后效性 的。因此,\(dp\) 做法直接假死,贪心做法半残。
二分解决此题显然不可做,单调性根本看不出来。这道题是求最优解,因此,肯定会用到以上三种算法的其中一种。乱搞做法也 不会 不可取的情况下,我们考虑最有希望的 贪心。
因为问题是有后效性的,所以在贪心过程中我们可能还会需要反悔。考虑反悔策略,方便起见只讨论初始值 \(a_i < b_i\) 的且对于任意位置 \(|a_i - b_i| = 1\) 的情况。首先,对于 只有一个位置 \(i\) 的情况,我们只能选择给它加上一个单位的泥土,花费为 \(x\) 。
接着,因为我们需要考虑反悔,所以假设接下来需要处理的位置为满足 \(b_j - a_j = 1\) 的位置 \(j\) 。这时我们有两种决策:
-
在第 \(j\) 个位置删去一个单位的泥土,花费为 \(y\)
-
从第 \(j\) 个位置移动一个单位的泥土给第 \(i\) 个位置,花费为 \(z \times |i - j|\)
一个显而易见的反悔策略是假如 \(z \times |i - j| < i + j\) ,那么我们就把第 \(j\) 个位置的单位泥土移动到第 \(i\) 个位置。但是,这样的反悔策略显然还是一个 贪心 策略,而反悔策略必须是一个能体现 动态规划 思想的策略,除非你能证明它是正确的。
已知的信息不足以推断出是否要对第 \(i\) 个位置进行反悔以及如何对第 \(i\) 个位置进行反悔,我们考虑是否能对式子下些功夫。显然,第 \(j\) 个位置的 花费 是 \(\min(y, z \times |i - j|)\) 。而此前我们假定位置 \(x\) 一定选择了增加一个单位的泥土,因此第二种决策还需要收回位置 \(x\) 的花费。也就是说,这次决策对于答案的 影响 是 \(\min(y, z \times |i - j| - x)\) 。
将上面这个式子拆开(忽略绝对值):
\(\quad z \times (j - i) - x\)
\(= z \times j - z \times i - x\)
\(= z \times j - (z \times i + x)\)
分类讨论,我们想要令这次决策产生的影响尽量小,要么 \(y < z \times |i - j| - x\) ,影响为 \(y\) ;要么令 \(z \times |i - j| - x\) 尽量小,也就是令 \(z \times i + x\) 尽量大。另一种情况同理。
那么做法是时候出现了:因为我们移动泥土一定从 \(a_i > b_i\) 的位置移动到 \(a_i < b_i\) 的位置,所以我们需要用两个大根堆来维护,q1
存储 \(a_i < b_i\) 位置的值,q2
存储 \(a_i > b_i\) 位置的值。这里维护的值比较特殊,我们将答案的影响拆成两部分,大根堆中维护的时上面式子中的 \((z \times i + x)\) 。之后需要统计某个位置 \(j\) 的影响的时候再把答案加上 \(j \times z - (z \times i + x)\) 。
接着对于某一个位置分类讨论:若 \(a_i < b_i\) ,我们就进行 \(b_i - a_i\) 次操作,设 q2
的堆顶为 \(v\),如果 q2
为空或者 \(i \times z - v \geq x\) ,那么此时我们选择不反悔直接加上一个单位的泥土更优。否则,我们给答案加上 \(i \times z - v\) 。
此处的 \(i \times z - v\) 表示的是把第 \(i\) 个位置的单位泥土移动到堆顶元素所在的位置所带来的花费,而大根堆所维护的含义是当前位置的影响,所以我们不可以直接把它压入堆顶。换句话说,现在的 \(i \times z - v\) 相当于上面式子中的 \(x\) ,而我们需要维护的是上面式子中的 \(z \times i + x\) 。所以我们应该压入的值是 \(i \times z - v + i \times z\) ,也就是 \(2 \times z \times i - v\) 。压入对应的大根堆即可。
\(a_i > b_i\) 的情况同理,调用 q1
压入 q2
即可。
参考代码
#include <iostream>
#include <queue>
using namespace std;
#define int long long
int n, a, b;
int x, y, z, ans;
priority_queue<int> q1, q2;
signed main() {
cin >> n >> x >> y >> z;
for (int i = 1; i <= n; i++) {
cin >> a >> b;
if (a < b) {
for (int j = 1; j <= b - a; j++) {
if (!q1.size() || i * z - q1.top() >= x) {
ans += x;
q2.push(i * z + x);
} else {
int v = q1.top();
q1.pop();
ans += (i * z - v);
q2.push(2 * i * z - v);
}
}
} else {
for (int j = 1; j <= a - b; j++) {
if (!q2.size() || i * z - q2.top() >= y) {
ans += y;
q1.push(i * z + y);
} else {
int v = q2.top();
q2.pop();
ans += (i * z - v);
q1.push(2 * i * z - v);
}
}
}
}
cout << ans << endl;
return 0;
}