题目链接:
题目描述
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点: 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 1≤L≤104;
- 对于 100 % 100\% 100% 的数据, 1 ≤ L ≤ 1 0 9 1\le L \le 10^9 1≤L≤109, 1 ≤ S ≤ T ≤ 10 1\le S\le T\le10 1≤S≤T≤10, 1 ≤ M ≤ 100 1\le M\le100 1≤M≤100。
算法思想:朴素版动态规划
如果不考虑 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 i−T≤j≤i−S,j即i - j
在S
到T
之间。 - 初始状态:
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 1≤L≤109)很大时该如何处理呢?我们发现虽然 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 (p−1)×(q−1)−1,因此所有大于等于 ( p − 1 ) × ( q − 1 ) (p-1)\times(q-1) (p−1)×(q−1)的数一定可以被 p p p和 q q q表示出来。
题目中给出 1 ≤ S ≤ T ≤ 10 1\le S\le T\le10 1≤S≤T≤10,那么当 T T T取最大值 10 10 10时, ( 10 − 1 ) × ( 9 − 1 ) = 72 (10-1)\times(9-1)=72 (10−1)×(9−1)=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;
}