【动态规划】【图解】NOIP 2005 提高组 过河
题目描述
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度)。坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数(包括S,T)。当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。
题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。
输入输出格式
输入格式:第一行有1个正整数L(1≤L≤10^9),表示独木桥的长度。
第二行有3个正整数S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离及桥上石子的个数,
其中1≤S≤T≤10,1≤M≤100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。
输出格式:一个整数,表示青蛙过河最少需要踩到的石子数。
数据范围
对于30%的数据,L≤10000;
对于全部的数据,L≤10^9。
用普通的动态可以轻松拿到30分
找每一个位置i的来源:从i - t ~ i - s的位置上的找一个最小需要踩得石头数量,如果当前位置是石头,则+1,否则不加
但是桥很长,没有别的处理一定会超时
不难发现,虽然桥很长,但是石头的数量很少,所以就会导致一个现象:两个石头中间可能会有很长很长一段空的,因为全都是空的,所以如果能减少在这段空的区域搜索的次数,一定能减少很多时间。
请看下图
假设总长为38,在2、3、4和36处有石头
青蛙的最小跳跃为2,最大跳跃为9(s = 2, t = 9)
f数组表示到第i个位置时最少要踩的石头的数量
红色,蓝色,黄色区域的长度都为t = 9
在蓝色区域和黄色区域上的位置的数字是没有算的必要的,后面的位置如果来源在蓝色或者黄色区域上的话,直接去红色区域对应的位置上找就可以了,因为蓝黄色区域上的数值等于红色区域上对应位置上的数值(都是红色区域上直接跳过来的)。
所以可以直接把图中上面的情况缩成下面的情况
注意:1、红色区域要保留,不能一起缩了,后面的位置还是要有来源的。
反映到程序上:两个石头位置间的差值如果 space >= t,就可以把这段距离缩成 space = t + space % t ;(space % t是剩下的一段,就是上图的32 ~ 35,t + 相当于保留了红色区域)
2、第一石头和起点之间,最后一个石头的终点之间的这两段不要忘了也可以缩。
3、题目描述中“当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。”要记得在终点后多处理一个t的长度。
剩下还有一些小细节看程序里批注即可
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 0x3f3f3f3f;
bool my_cmp(int x, int y)
{
return x < y;
}
int mp[2005], rock[2005]; //记录最小的要踩的石头数量, 记录每个(缩完后)的位置上有没有石头,有则为1
int r[150];
int l;
int n;
int main()
{
int pre;
int w;
int mmin;
int space;
int s, t;
int i, j;
cin >> l;
cin >> s >> t >> n;
for (i = 1; i <= n; i++)
cin >> r[i];//石头位置
memset(mp, MAXN, sizeof(mp));
sort(r + 1, r + n + 1, my_cmp); //输入没说是按顺序输入石头的位置
r[0] = 0; //第一个石头和起点中间的那段,这里把起点假定为有石头
r[n + 1] = l;//最后一个石头和终点中间的那段,这里把终点处也假定为有石头
pre = 0;//记录一下上一个石头的位置,因为缩完之后输入进来的数组中的石头位置已经不对了 ,最后这个数会加为缩完后的桥的长度
for (i = 1; i <= n + 1; i++)//n + 1把最后一个石头和终点中间的算上
{
space = r[i] - r[i - 1]; //两个石头中间的距离
if (space >= t)
space = space % t + t; //缩
w = pre + space;//当前石头的位置,上一个位置加上缩完后的两个石头间的距离
rock[w] = 1;//记录缩完后石头的位置
pre = w;//记录上一个石头的位置
}
rock[pre] = 0;//把最后终点处假定的石头抹去,(起点处假定的石头不用抹去因为对后面无影响)
mp[0] = 0;//起点为0
for (i = s; i <= pre + t; i++) //从最小的跳跃距离开始,然后多处理一个t的长度
{
mmin = MAXN;
for (j = max(0, i - t); j <= i - s; j++)//找来源
mmin = min(mp[j], mmin);//找到来源中最小的
mp[i] = rock[i] + mmin;//计算当前的
}
mmin = MAXN;
for (i = pre; i <= pre + t; i++)//从终点到终点 + t的位置上找一个最小的
mmin = min(mp[i], mmin);
cout << mmin << endl;//输出结果
return 0;
}