算法——过河

14 篇文章 1 订阅
该博客探讨了一道动态规划题目,涉及青蛙在独木桥上跳跃避开石子的问题。博主通过压缩数据和动态规划算法,解决了当跳跃距离范围较大而石子数量有限的情况。博主分享了代码实现过程,包括数据压缩、动态规划状态转移方程的建立以及如何找到最小值。此外,还特别提到了当跳跃距离等于最大距离时的特殊情况处理。
摘要由CSDN通过智能技术生成

来源: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 2tm的量级。数组就开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++;
			}
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值