第2题 - 登山鞋(C++实现)
第2题 - 登山鞋(C++实现)
题目内容
小明正在玩一个游戏:
游戏中有一排山峰,他需要从左到右依次经过 $n$ 座山,其中第 $i$ 座山的高度为 $h_i$。
在游戏开始之前,小明需要装备一双 登山鞋。假设鞋子的 耐久度 为 $x$,那么对于任意相邻的两座山,如果高度差 $|h_{i+1}-h_i| > x$,小明就无法攀爬过去。
小明在游戏开始前,可以进行 $K$ 次操作,每次可以把任意一座山的高度改为任意非负整数。
问题:小明能够装备的登山鞋的 最低耐久度 可以是多少?
输入描述
-
第一行:一个正整数 $T$,表示数据组数。
-
对于每组数据:
- 第一行输入两个正整数 $n, K$,表示山的数量和小明购买的操作次数。
- 第二行输入 $n$ 个整数,表示每座山的高度。
输出描述
- 对于每组数据,输出一个整数,表示小明经过若干次操作之后,可以装备的登山鞋的 最低耐久度。
样例
输入
3
1 1
2
5 1
1 2 4 7 8
5 3
6 4 7 10 5
输出
0
2
1
样例解释
- 第一组:只有一座山,不需要鞋子,答案是 0。
- 第二组:可以把高度改为
[1,2,4,6,8]
,那么相邻高度差最大为 2。 - 第三组:可以改为
[6,6,7,8,9]
,那么相邻高度差最大为 1。
算法思路
关键观察
- 修改最多 $K$ 个位置,等价于 至少保留 $n-K$ 个位置 不变。
- 被修改的位置可以随意设置,用来“补”出来满足条件。
因此问题转化为:
给定一个耐久度 $x$,能否保留至少 $n-K$ 个位置,使得相邻保留位置 $i,j$ 满足:
∣hj−hi∣≤(j−i)×x |h_j - h_i| \leq (j-i) \times x ∣hj−hi∣≤(j−i)×x
这样就能通过“线性插值”把中间修改的山峰补出来。
判定函数(feasible)
-
类似 最长上升子序列(LIS)的 DP 思路:
- 设
dp[i]
表示以第 $i$ 座山结尾的“可保留子序列”最大长度。 - 转移条件:
如果 $|h[i]-h[j]| \leq (i-j)\times x$,则dp[i] = max(dp[i], dp[j]+1)
。
- 设
-
如果最后的最大长度 ≥ $n-K$,说明耐久度 $x$ 可行。
时间复杂度:单次 DP 为 $O(n^2)$。
二分答案
-
耐久度 $x$ 的最小值单调:
- 如果 $x$ 可行,那么更大的 $x$ 也一定可行。
-
用二分法找到最小的 $x$。
-
上界 $R$ 可以取 相邻差的最大值(不修改的情况下必然可行)。
整体复杂度:
- 二分次数 $O(\log R)$
- 每次判定 $O(n^2)$
- 总复杂度 $O(n^2 \log R)$
C++代码实现
#include <bits/stdc++.h>
using namespace std;// 判定函数:给定耐久度 x,是否能保留至少 n-K 个位置
bool feasible(const vector<int>& h, int n, int K, int x) {int need = n - K; // 至少需要保留的数量vector<int> dp(n, 1); // dp[i]:以 i 结尾的可保留子序列长度int best = 1;for (int i = 0; i < n; i++) {for (int j = 0; j < i; j++) {// 如果相邻两点满足 |h[i]-h[j]| <= (i-j)*xif (abs(h[i] - h[j]) <= x * (i - j)) {dp[i] = max(dp[i], dp[j] + 1);}}best = max(best, dp[i]);if (best >= need) return true; // 提前结束}return best >= need;
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int T;cin >> T;while (T--) {int n, K;cin >> n >> K;vector<int> h(n);for (int i = 0; i < n; i++) cin >> h[i];// 边界情况:只有一座山if (n <= 1) {cout << 0 << "\n";continue;}// 计算最大相邻差,作为二分上界int R = 0;for (int i = 1; i < n; i++) {R = max(R, abs(h[i] - h[i - 1]));}int l = 0, r = R;while (l < r) {int mid = (l + r) / 2;if (feasible(h, n, K, mid)) r = mid;else l = mid + 1;}cout << l << "\n";}return 0;
}
示例运行
输入:
3
1 1
2
5 1
1 2 4 7 8
5 3
6 4 7 10 5
输出:
0
2
1
与题目样例完全一致。
总结
这道题的关键在于:
- 把“修改 K 个位置”转化为“保留 n-K 个位置”。
- 利用 DP + 二分的方法,判定某个耐久度是否可行。
- 通过二分搜索找到最小的耐久度。
该方法时间复杂度约为 $O(n^2 \log R)$,在数据规模中是可行的。