【动态规划】【图解】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,否则不加

但是桥很长,没有别的处理一定会超时
不难发现,虽然桥很长,但是石头的数量很少,所以就会导致一个现象:两个石头中间可能会有很长很长一段空的,因为全都是空的,所以如果能减少在这段空的区域搜索的次数,一定能减少很多时间。
请看下图
【动态规划】【图解】NOIP 2005 提高组 过河

假设总长为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;
}