来源:1002 过河
题目描述
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度)。坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数(包括S,T)。当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。
题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。
对于30%的数据,L < = 10000;
对于全部的数据,L < = 10^9。
输入数据
输入的第一行有一个正整数 L (1≤L≤109), 表示独木桥的长度。第二行有三个正整数 S,T,M, 分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中 1≤S≤T≤10,1≤M≤100 。第三行有 M 个不同的正整数分别表示这 M 个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。
输出数据
输出只包括一个整数,表示青蛙过河最少需要踩到的石子数。
样例输入
10
2 3 5
2 3 5 6 7
样例输出
2
思路:动态规划问题,但是L的范围太大,数组没办法开到 1 0 9 10^9 109这么大的。ps 为此我专门尝试了一下,数组最大开到1e5,再大就会栈溢出。于是考虑压缩数组。
压缩数组
仔细审题,石头数最多100,但是距离达到十亿级别,于是想到两个石头之间的距离可以压缩,且要保证压缩后对结果没有影响。
设距离为d,若d>t,d%t+t等效于d。无非就是原来跳d/t次t长度,现在全部算为1次t长度。所以距离从
1
0
9
10^9
109这么大的量级,压缩成
2
∗
t
∗
m
2*t*m
2∗t∗m的量级。数组就开20,000。
每压缩一次记录一个石头,放在stone数组里。
for (int i = 1; i <= m + 1; i++) {
if (a[i] - a[i - 1] >= t) {
cnt += (a[i] - a[i - 1]) % t + t;
}
else cnt += a[i] - a[i - 1];
stone[cnt] = 1;
}
stone[cnt] = 0;
最后的stone[cnt] = 0;是因为我们把距离L也看做了一个石头间距,放在最后一个位置,但由于终点是没有石头的,需要单独将这个位置设为0;
动态规划初始化
初始化的问题,我想了很久。这道题是求最小值,于是数组应该初始化为所有最小值最大也不可能取到的一个数,比如10000;
//动态规划初始化
for (int i = 1; i <= cnt + t; i++) {
dp[i] = 10000;
}
dp[0]=0;
动态规划转移方程
如果i==j,那么说明青蛙从起点直接跳到了i这个位置,之前肯定没有踩到过石头,于是设置dp[0]=0。
//动态规划:将所有位置的最小值找到并存储
for (int i = 1; i < cnt + t; i++) {
for (int j = s; j <= t; j++) {
if (i - j >= 0)
dp[i] = min(dp[i], dp[i - j] + stone[i]);
}
}
终于明白了,昨天我陷入了思维误区,因为题目给的案例比较特殊,我直接以为在跳转的边界会有石头,看来还是需要多审题!及时转变思路。
找到最小值
题目说"当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥",所以最小值应该在终点cnt到cnt+t-1的范围内产生
//动态规划:找到合理最小值
int res = dp[cnt];
for (int i = cnt + 1; i < cnt + t; i++) {
res = min(res, dp[i]);
}
完整代码
#include <algorithm>
#include <iostream>
using namespace std;
int a[100];
int stone[20000];
int dp[20000];
int main() {
int n, s, t, m;
cin >> n;
cin >> s >> t >> m;
for (int i = 1; i <= m; i++) {
cin >> a[i];
}
a[m + 1] = n;
sort(a, a + m + 2);
int cnt = 0;
//压缩数据:压缩石头间距
for (int i = 1; i <= m + 1; i++) {
if (a[i] - a[i - 1] >= t) {
cnt += (a[i] - a[i - 1]) % t + t;
}
else cnt += a[i] - a[i - 1];
stone[cnt] = 1;
}
stone[cnt] = 0;
stone[0] = 0;
//动态规划初始化
for (int i = 1; i <= cnt + t; i++) {
dp[i] = 10000;
}
//动态规划:将所有位置的最小值找到并存储
for (int i = 1; i < cnt + t; i++) {
for (int j = s; j <= t; j++) {
if (i - j >= 0)
dp[i] = min(dp[i], dp[i - j] + stone[i]);
}
}
//动态规划:找到合理最小值
int res = dp[cnt];
for (int i = cnt + 1; i < cnt + t; i++) {
res = min(res, dp[i]);
}
cout << res;
}
进一步
看到有的题解里说,当s=t时,只需考查石子是否是s的倍数即可。这种情况单独考查。
也就是看石头的位置是不是在步长的倍数上。是的话,结果就加一。
if (s == t) {
for (int i = 1; i <= m; i++){
if (a[i] % s==0) {
res++;
}
}
}