洛谷P1052 [NOIP 2005 提高组] 过河 题解

目录

题目描述

题目分析

解题思路

代码实现

代码解析

总结


题目描述

在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点: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 提高组第二题


题目分析

这道题目描述了一只青蛙要过河,河中有若干石头,青蛙每次跳跃有一定的步长限制。我们需要计算青蛙最少需要踩多少块石头才能过河。这是一个典型的动态规划问题,涉及到状态转移和路径优化。

解题思路

  1. 问题建模:将河流看作一个线性区间,石头的位置作为关键点。青蛙的跳跃距离受限于给定的步长范围。

  2. 动态规划:使用动态规划来记录到达每个位置所需的最小石头数。状态转移方程需要考虑所有可能的跳跃步长。

  3. 路径压缩:由于石头位置可能很稀疏,直接处理所有点会导致效率问题,需要进行路径压缩,只处理关键点。

  4. 边界处理:特别注意起点和终点的处理,确保青蛙能够从起点出发并到达终点。

代码实现

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;
}

代码解析

  1. 输入处理:首先读取河的长度L,青蛙跳跃的最小步长S和最大步长T,以及石头的数量M。然后读取每个石头的具体位置。

  2. 特殊情况处理:当S等于T时,青蛙的跳跃步长固定,此时只需要统计位于跳跃步长整数倍位置的石头数量即可。

  3. 路径压缩:为了处理石头稀疏分布的情况,我们对石头位置进行压缩。如果两个相邻石头之间的距离超过100,我们将其压缩为100,以减少计算量。

  4. 动态规划初始化:初始化dp数组,dp[i]表示到达位置i所需的最小石头数。起点dp[0]初始化为0。

  5. 状态转移:对于每个位置i,考虑从i-S到i-T的所有可能的前驱位置,更新dp[i]的最小值。如果当前位置有石头,则石头数加1。

  6. 结果输出:在最后一段区间内寻找最小的石头数,输出结果。

复杂度分析

  • 时间复杂度:O(L*T),其中L是压缩后的河长,T是最大步长。

  • 空间复杂度:O(L),用于存储dp数组和石头位置标记。

优化与注意事项

  1. 路径压缩:通过压缩石头位置,将原始问题转化为一个更紧凑的问题,显著提高了算法效率。

  2. 边界条件:特别注意S等于T的特殊情况,此时可以直接计算而不需要复杂的动态规划。

  3. 数组大小:合理设置数组大小,避免内存溢出或浪费。

总结

这道题目通过动态规划和路径压缩的技巧,有效地解决了青蛙过河问题。关键在于如何将原始问题转化为适合动态规划的形式,并通过优化减少计算量。理解状态转移和路径压缩的原理是解决此类问题的关键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值