目录
题目描述
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,⋯,L(其中 L 是桥的长度)。坐标为 0 的点表示桥的起点,坐标为 L 的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是 S 到 T 之间的任意正整数(包括 S,T)。当青蛙跳到或跳过坐标为 L 的点时,就算青蛙已经跳出了独木桥。
题目给出独木桥的长度 L,青蛙跳跃的距离范围 S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。
输入格式
输入共三行,
- 第一行有 1 个正整数 L,表示独木桥的长度。
- 第二行有 3 个正整数 S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离及桥上石子的个数。
- 第三行有 M 个不同的正整数分别表示这 M 个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。
输出格式
一个整数,表示青蛙过河最少需要踩到的石子数。
输入输出样例
输入 #1
10 2 3 5 2 3 5 6 7
输出 #1
2
说明/提示
【数据范围】
- 对于 30% 的数据,1≤L≤104;
- 对于 100% 的数据,1≤L≤109,1≤S≤T≤10,1≤M≤100。
【题目来源】
NOIP 2005 提高组第二题
题目分析
这道题目描述了一只青蛙要过河,河中有若干石头,青蛙每次跳跃有一定的步长限制。我们需要计算青蛙最少需要踩多少块石头才能过河。这是一个典型的动态规划问题,涉及到状态转移和路径优化。
解题思路
-
问题建模:将河流看作一个线性区间,石头的位置作为关键点。青蛙的跳跃距离受限于给定的步长范围。
-
动态规划:使用动态规划来记录到达每个位置所需的最小石头数。状态转移方程需要考虑所有可能的跳跃步长。
-
路径压缩:由于石头位置可能很稀疏,直接处理所有点会导致效率问题,需要进行路径压缩,只处理关键点。
-
边界处理:特别注意起点和终点的处理,确保青蛙能够从起点出发并到达终点。
代码实现
cpp
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 10010; // 最大石头数
const int MAXL = 1000000; // 最大河长
const int INF = 0x3f3f3f3f; // 无穷大
int L, S, T, M; // 河长,跳跃最小步长,最大步长,石头数
int stone[MAXN]; // 石头位置
int dp[MAXL]; // dp数组
bool has_stone[MAXL]; // 标记是否有石头
int main() {
cin >> L >> S >> T >> M;
for (int i = 1; i <= M; ++i) {
cin >> stone[i];
}
// 排序石头位置
sort(stone + 1, stone + M + 1);
// 路径压缩
if (S == T) { // 特殊处理步长固定的情况
int cnt = 0;
for (int i = 1; i <= M; ++i) {
if (stone[i] % S == 0) {
cnt++;
}
}
cout << cnt << endl;
return 0;
}
// 压缩石头位置
int last = 0;
for (int i = 1; i <= M; ++i) {
int diff = stone[i] - stone[i - 1];
if (diff > 100) { // 压缩距离超过100的部分
diff = 100;
}
stone[i] = stone[i - 1] + diff;
has_stone[stone[i]] = true;
}
L = stone[M] + 100; // 更新河长
// 初始化dp数组
memset(dp, INF, sizeof(dp));
dp[0] = 0; // 起点
// 动态规划
for (int i = 1; i <= L; ++i) {
for (int j = S; j <= T; ++j) {
if (i - j >= 0) {
dp[i] = min(dp[i], dp[i - j] + (has_stone[i] ? 1 : 0));
}
}
}
// 寻找最小石头数
int ans = INF;
for (int i = stone[M]; i <= L; ++i) {
ans = min(ans, dp[i]);
}
cout << ans << endl;
return 0;
}
代码解析
-
输入处理:首先读取河的长度L,青蛙跳跃的最小步长S和最大步长T,以及石头的数量M。然后读取每个石头的具体位置。
-
特殊情况处理:当S等于T时,青蛙的跳跃步长固定,此时只需要统计位于跳跃步长整数倍位置的石头数量即可。
-
路径压缩:为了处理石头稀疏分布的情况,我们对石头位置进行压缩。如果两个相邻石头之间的距离超过100,我们将其压缩为100,以减少计算量。
-
动态规划初始化:初始化dp数组,dp[i]表示到达位置i所需的最小石头数。起点dp[0]初始化为0。
-
状态转移:对于每个位置i,考虑从i-S到i-T的所有可能的前驱位置,更新dp[i]的最小值。如果当前位置有石头,则石头数加1。
-
结果输出:在最后一段区间内寻找最小的石头数,输出结果。
复杂度分析
-
时间复杂度:O(L*T),其中L是压缩后的河长,T是最大步长。
-
空间复杂度:O(L),用于存储dp数组和石头位置标记。
优化与注意事项
-
路径压缩:通过压缩石头位置,将原始问题转化为一个更紧凑的问题,显著提高了算法效率。
-
边界条件:特别注意S等于T的特殊情况,此时可以直接计算而不需要复杂的动态规划。
-
数组大小:合理设置数组大小,避免内存溢出或浪费。
总结
这道题目通过动态规划和路径压缩的技巧,有效地解决了青蛙过河问题。关键在于如何将原始问题转化为适合动态规划的形式,并通过优化减少计算量。理解状态转移和路径压缩的原理是解决此类问题的关键。

344

被折叠的 条评论
为什么被折叠?



