这道题的核心在于理解小球在数轴上的移动规则和条件约束。我们先从题目描述中提取几个关键信息:
关键观察:由于所有条件都要求x_i > y_i,这意味着每个条件都要求小球至少有一次"折返"动作 - 即从x_i向右移动后,必须向左移动经过y_i。
最直观的贪心思路是:尽可能减少转向次数,因为每次转向都需要付出代价。那么如何用最少的转向满足所有条件?
经过分析可以发现:
但题目要求的是最小化总代价,而不仅仅是转向次数。因此我们需要在[max_x, n]区间内选择转向代价a_i最小的点进行转向。
为什么这个策略是正确的?我们可以从两个方面证明:
充分性:选择max_x之后的点转向确实能满足所有条件。因为:
最优性:这是代价最小的方案。因为:
根据上述思路,基础实现可以分为三步:
原始代码已经给出了基本实现,但我们可以做一些优化:
cpp复制#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
int a[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
int max_x = 0;
for (int i = 0; i < m; ++i) {
int x, y;
cin >> x >> y;
max_x = max(max_x, x);
}
int min_cost = INT_MAX;
for (int i = max_x; i <= n; ++i) {
min_cost = min(min_cost, a[i]);
}
cout << min_cost << endl;
return 0;
}
对于n,m ≤ 2×10^5的数据范围,这个复杂度是完全可接受的。
如果题目数据量更大,我们可以预处理a数组的前缀最小值,将查询区间最小值的复杂度降到O(1):
cpp复制// 预处理前缀最小值数组
vector<int> prefix_min(n + 1);
prefix_min[0] = INT_MAX;
for (int i = 1; i <= n; ++i) {
prefix_min[i] = min(prefix_min[i - 1], a[i]);
}
// 查询[max_x, n]的最小值就是prefix_min[n] - prefix_min[max_x - 1]
int min_cost = prefix_min[n];
if (max_x > 1) {
min_cost = min(min_cost, prefix_min[max_x - 1]);
}
不过对于本题的数据范围,这种优化可能不会带来明显的性能提升。
在实现这个算法时,有几个边界情况需要特别注意:
实际编码时,建议添加这些边界条件的检查,特别是在竞赛环境中,有时题目给出的约束可能与实际测试数据不符。
这个问题可以有几种有趣的变种:
对于当前这个简单版本,贪心算法已经提供了最优解。理解这个简单案例有助于我们处理更复杂的变种问题。
如果你觉得这个问题有趣,可以尝试解决以下类似题目:
这些题目都能帮助你更好地理解贪心算法的应用场景和解题思路。
在解决这类贪心算法问题时,我总结了几个关键步骤:
在实际编程竞赛中,这类问题通常需要快速准确地实现。建议平时多练习类似的贪心问题,培养快速发现贪心策略的能力。
最后,这道题的AC代码虽然简短,但背后的思考过程很有价值。贪心算法往往看起来简单,但要发现正确的贪心策略需要敏锐的观察力和严谨的证明。