NOIP2005提高组第二轮T2:过河

题目链接:

NOIP2005提高组第二轮T2:过河

题目描述

在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点: 0 , 1 , ⋯   , L 0,1,\cdots,L 0,1,,L(其中 L L L 是桥的长度)。坐标为 0 0 0 的点表示桥的起点,坐标为 L L L 的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是 S S S T T T 之间的任意正整数(包括 S , T S,T S,T)。当青蛙跳到或跳过坐标为 L L L 的点时,就算青蛙已经跳出了独木桥。

题目给出独木桥的长度 L L L,青蛙跳跃的距离范围 S , T S,T S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。

输入格式

输入共三行,

  • 第一行有 1 1 1 个正整数 L L L,表示独木桥的长度。
  • 第二行有 3 3 3 个正整数 S , T , M S,T,M S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离及桥上石子的个数。
  • 第三行有 M M M 个不同的正整数分别表示这 M M M 个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。

输出格式

一个整数,表示青蛙过河最少需要踩到的石子数。

样例 #1

样例输入 #1

10
2 3 5
2 3 5 6 7

样例输出 #1

2

提示

【数据范围】

  • 对于 30 % 30\% 30% 的数据, 1 ≤ L ≤ 1 0 4 1\le L \le 10^4 1L104
  • 对于 100 % 100\% 100% 的数据, 1 ≤ L ≤ 1 0 9 1\le L \le 10^9 1L109 1 ≤ S ≤ T ≤ 10 1\le S\le T\le10 1ST10 1 ≤ M ≤ 100 1\le M\le100 1M100

算法思想:朴素版动态规划

如果不考虑 L L L 的范围,那么就是一道简单的DP问题:

  • 状态表示:f[i] 表示走到位置 i时踩到的石头个数的最小值;
  • 状态转移方程: f[i] = min(f[j]) + w[i], 其中 w[i] 表示第 i 个位置是否有石头,其中 i − T ≤ j ≤ i − S i-T\le j \le i - S iTjiS,j即i - jST 之间。
  • 初始状态:f[i]为无穷大,f[0] = 0

时间复杂度

O ( T × L ) O(T\times L) O(T×L)

代码实现

#include <iostream>
using namespace std;
const int N = 3e6;
//w[i]表示i点是否有石头
int f[N], w[N];
int main()
{
    int L, S, T, m, x;
    cin >> L >> S >> T >> m;
    for(int i = 1; i <= m; i ++) 
    {
        cin >> x;
        w[x] = 1;
    }
    //枚举每个位置,计算状态。注意:枚举到L+T-1
    for(int i = 1; i < L + T; i ++)
    {
        f[i] = 1e9;
        for(int j = i - T; j <= i - S; j ++)
        {
            if(j >= 0) 
                f[i] = min(f[i], f[j] + w[i]);
        }
    }
    int ans = m;
    for(int i = L; i < L + T; i ++)
        ans = min(ans, f[i]);
    cout << ans;
    return 0;
}

算法思想:优化版动态规划

那么当 L L L 1 ≤ L ≤ 1 0 9 1\le L \le 10^9 1L109)很大时该如何处理呢?我们发现虽然 L L L很大,但石头总数很少,最多只有 100 100 100,因此两个石头之间可能有很长的空隙。那么可以分为两种情况来考虑:

  • 如果S == T,也就是青蛙每次跳过的距离都相同,那么走法惟一,如果石头位置是 S S S 的整数倍则一定会被踩,否则一定不会被踩,直接遍历一遍所有石头即可。

  • 如果S != T,那么青蛙跳过的点一定是能被 S , S + 1 , S + 2 , . . . , T S,S+1,S+2,...,T S,S+1,S+2,...,T组合表示的数,例如当 S = 2 , T = 3 S=2,T=3 S=2,T=3,那么除了 1 1 1之外,其它各点都能由 2 2 2 3 3 3组合表示出来,如 4 = 2 + 2 , 5 = 2 + 3 , 6 = 3 + 3 , 7 = 2 + 2 + 3... 4=2+2,5=2+3,6=3+3,7=2+2+3... 4=2+2,5=2+3,6=3+3,7=2+2+3...,这些点都是青蛙可以到达的位置。哪些数不能被 S , S + 1 , S + 2 , . . . , T S,S+1,S+2,...,T S,S+1,S+2,...,T组合表示出来呢?

    对于两个互质的数 p p p q q q,不能被表示的数的最大值是 ( p − 1 ) × ( q − 1 ) − 1 (p-1)\times(q-1)-1 (p1)×(q1)1,因此所有大于等于 ( p − 1 ) × ( q − 1 ) (p-1)\times(q-1) (p1)×(q1)的数一定可以被 p p p q q q表示出来。

    题目中给出 1 ≤ S ≤ T ≤ 10 1\le S\le T\le10 1ST10,那么当 T T T取最大值 10 10 10时, ( 10 − 1 ) × ( 9 − 1 ) = 72 (10-1)\times(9-1)=72 (101)×(91)=72,因此所有大于等于 72 72 72的数,一定可以被 S , S + 1 , S + 2 , . . . , T S,S+1,S+2,...,T S,S+1,S+2,...,T表示出来。

    根据上述分析,当第一次越过第 i i i个石头时,青蛙的位置一定在该石头右侧 10 10 10步以内,如下图所示左侧红色区域处;当即将跳过第 i + 1 i+1 i+1 个石头时,青蛙一定在第 i + 1 i+1 i+1 个石头左侧 10 10 10步以内,如下图右侧红色区域处。那么当中间绿色部分的长度大于 72 72 72时,可以从左侧红色线段内的任意一点,跳到右侧红色区域的任意一点。
    在这里插入图片描述
    也就是说当两个石头之间的距离很大时,如果将两个石头之间的距离缩短为 100 100 100(红色+绿色),那么得到的结果是等价。那么此时最多只会用到 100 × 100 100\times100 100×100个位置,时间复杂度和空间复杂度就满足要求了。

时间复杂度

每个两个石头之间最多会添加 100 100 100个位置,因此总共最多有 10000 10000 10000个状态,计算每个状态最多需要 10 10 10次计算,因此总计算量是 m × T 3 = 1 0 5 m\times T^3=10^5 m×T3=105

代码实现

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e4 + 50;
//w[i]表示i点是否有石头
int a[N], f[N], w[N];
int main()
{
    int L, S, T, m;
    cin >> L >> S >> T >> m;
    for(int i = 1; i <= m; i ++) cin >> a[i];
    //青蛙每次跳过的距离都相同,那么走法惟一
    //如果石头位置是S的整数倍则一定会被踩,否则一定不会被踩
    if(S == T)
    {
        int ans = 0;
        for(int i = 1; i <= m; i ++)
            if(a[i] % S == 0) ans ++;
        cout << ans;
        return 0;
    }
    sort(a + 1, a + m + 1);
    int last = 0, k = 0;
    //枚举每个石头,
    for(int i = 1; i <= m; i ++)
    {
        //将上个石头和第i个石头之间的位置标记为没有石头,如果该位置大于100,则忽略
        for(int j = 0; j < min(a[i] - last, 100); j ++)
            w[++ k] = 0; //将该位置标记为没有石头
        w[k] = 1; //将第i个石头移动到k位置
        last = a[i]; //更新上一个石头的位置
    }
    for(int i = 1; i < k + T; i ++)
    {
        f[i] = 1e9;
        for(int j = i - T; j <= i - S; j ++)
        {
            if(j >= 0) f[i] = min(f[i], f[j] + w[i]);
        }
    }
    int ans = m; //求最小值
    for(int i = k + 1; i < k + T; i ++)
        ans = min(ans, f[i]);
    cout << ans;
    
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值