过河
题目描述
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。
在桥上有一些石子,青蛙很讨厌踩在这些石子上。
由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点: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 过河

浙公网安备 33010602011771号