Loading

【题解】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\) 。这时我们有两种决策:

  1. 在第 \(j\) 个位置删去一个单位的泥土,花费为 \(y\)

  2. 从第 \(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;
}
posted @ 2021-07-24 23:35  kymru  阅读(45)  评论(0编辑  收藏  举报