过河

题目描述

在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。

在桥上有一些石子,青蛙很讨厌踩在这些石子上。

由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,......,L(其中 L是桥的长度)。

坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。

青蛙从桥的起点开始,不停的向终点方向跳跃。

一次跳跃的距离是S到T之间的任意正整数(包括 S,T)。

当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。

题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。

你的任务是确定青蛙要想过河,最少需要踩到的石子数。

输入格式

输入文件的第一行有一个正整数 L,表示独木桥的长度。

第二行有三个正整数 S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数。

第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。

所有相邻的整数之间用一个空格隔开。

输出格式

输出文件只包括一个整数,表示青蛙过河最少需要踩到的石子数。

数据范围

1≤L≤10^9
1≤S≤T≤10
1≤M≤100

输入样例:

10
2 3 5
2 3 5 6 7

输出样例:

2

解题思路

每一个点,都可以从前面第S到前面第T个跳过来,可以直接根据动态规划的思路想到一个dp转移方程:dp[i] = min(dp[i - j] + stone[i], dp[i]) j∈[i-S,i-T],stone[i]表示该点有没有石头。

但是L的范围是10^9,动态规划dp数组太长了。发现1≤M≤100,所以其实中间会有很多没有石头的点。

如果两个石头中间的间隔,超过了S和T的最小公倍数,那么可以把这两个点的距离缩短为S和T的最小公倍数。
理由是距离超过最小公倍数的任意一个点,都可以达到,而且中间没有石头,不会影响踩到的石子数这个结果。

然后因为题目要求就算青蛙已经跳出了独木桥也可以,所以最后要从dp[L]到dp[L+S-1]中取最小值,整个dp数组的长度应该是dp[L+S]。

有个特殊情况就是如果S等于T,那么只需要看M个数中有多少个是S的倍数即可。

代码

#include <set>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

int GCD(int a, int b);// 求最大公约数
int LCM(int a, int b); // 求最大公约数
int CrossRiver(int L, int S, int T, std::vector<int> stonevec);

int main()
{
	int L, S, T, M;
	cin >> L >> S >> T >> M;
	std::vector<int> stonevec(M);
	for (int i = 0; i < M; ++i)
	{
		cin >> stonevec[i];
	}
	std::cout << CrossRiver(L, S, T, stonevec) << std::endl;
	return 0;
}

int CrossRiver(int L, int S, int T, std::vector<int> stonevec)
{
	if (S == T)
	{
		//如果S==T,只要看有多少是S的倍数即可
		int num = 0;
		std::for_each(stonevec.begin(), stonevec.end(), [=, &num](int val) {
			if (!(val % S))
				++num;
		});
		return num;
	}

	//先排序
	std::sort(stonevec.begin(), stonevec.end());
	//L肯定是最大
	stonevec.push_back(L);//这里是为了把L也缩小,所以也放到了石头里
	int preold = 0;//前一个数之前的值
	int prenow = 0;//前一个数现在的值
	int needsub = 0;//后面的数全要减去这么多
	int lcmnum = LCM(S, T);//最小公倍数
	for (auto &value : stonevec)
	{
		if (value - preold > lcmnum)//跟原本的值比较,把距离大于lcm的都设置成lcm
		{
			needsub = value - (prenow + lcmnum);//修改后面的数要减去的值
			preold = value;
			value = prenow + lcmnum;
		}
		else
		{
			preold = value;
			value -= needsub;
		}
		prenow = value;
	}

	L = stonevec.back();
	stonevec.pop_back();
	//放到一个set中便于判断是否有石头
	std::set<int> stoneset(stonevec.begin(), stonevec.end());
	int dplen = L + S;
	std::vector<int> dp(dplen, 100);//最后一个下标为L+S-1 因为1≤M≤100,所以都初始化成100
	dp[0] = 0;//初始化dp
	for (int i = 1; i < dplen; ++i)
	{
		//j∈[i-T,i-S]
		for (int j = i - T; j <= i - S; ++j)
		{
			if (j >= 0)
			{
				//dp[i] = min(dp[i - j] + stone[i], dp[i])
				dp[i] = std::min(dp[j] + (int)stoneset.count(i), dp[i]);
				if (!dp[i])//如果已经是0了则没必要继续求min了
					break;
			}
		}
	}

	//因为题目要求就算青蛙已经跳出了独木桥也可以,所以最后要从`dp[L]`到`dp[L+S-1]`中取最小值
	int ret = dp[L];
	for (int i = L + 1; i <= L + S - 1; ++i)
	{
		ret = std::min(ret, dp[i]);
	}

	return ret;
}

int GCD(int a, int b) {           // 求最大公约数
	int temp = 0;
	while (b != 0) {
		temp = a;
		a = b;
		b = temp % b;
	}
	return a;
}
int LCM(int a, int b) {           // 求最小公倍数
	int gcd = GCD(a, b);
	return a * b / gcd;
}

题目链接:过河
参考思路:luogu p1052 过河

posted @ 2023-11-27 15:55  钟离默  阅读(58)  评论(0)    收藏  举报