天天看点

[Wikioi 1105][NOIP 2005提高组]过河

题目描述 Description

在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度)。坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数(包括S,T)。当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。

题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。

输入描述 Input Description

输入第一行有一个正整数L(1<=L<=109),表示独木桥的长度。第二行有三个正整数S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中1<=S<=T<=10,1<=M<=100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。

输出描述 Output Description

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

样例输入 Sample Input

10

2 35

2 3567

样例输出 Sample Output

2

数据范围及提示 Data Size & Hint

数据规模

对于30%的数据,L<=10000;

对于全部的数据,L<=109。

题目思路

此题是一道典型的DP题,动规方程f[i]=min(f[i])+a[i](f[i]=到达i点所需最少石子数,a[i]=1表示i点有石子,a[i]=0表示i点无石子),方程比较简单,但考虑到此题数据范围太大,单纯使用这样的普通DP必然超时,而可能很长一段距离里都没有石子,那么可以考虑使用状态压缩型DP,如下图所示

[Wikioi 1105][NOIP 2005提高组]过河

第2、第3个点间距离超过了青蛙最大跳跃距离,则青蛙站在第2个石子上不可能跳到第3个石子去,第2、3个点间距离大于青蛙最大跳跃距离,而这个距离如果等于其最大跳跃距离(即一个临界值),青蛙一样跳不过去,因此该距离可以压缩为青蛙最大跳跃距离,如果两个石子间的距离小于其最大跳跃距离,就不能压缩(压缩后距离反而变大了)

压缩完成后,就可以将此题化为一般的DP题解决掉,然后AC

顺便还要补充一下,最后的DP循环为什么要循环到压缩距离+最大跳跃距离,由题意可得青蛙不一定要落在终点,它可以落在终点后方,而青蛙站在终点(还是临界值)跳时,最远只能落在压缩距离+最大跳跃距离这个位置,因此要循环到这里来,图中“永远不会溢出”打错了。。。

下面是代码

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#define MAXN 100020
#define INF 100000000
using namespace std;
int min(int a,int b)
{
	if(a<b)
		return a;
	return b;
}
int main()
{
	int stone[200]; //stone[i]表示第i个石子距起点的长度
	int pos[MAXN]={0};//pos[i]=1表示距起点距离为i的点有一个石子
	int f[MAXN]; //f[i]=到距起点距离为i的点要经过的最少石子数
	memset(f,0,sizeof(f));
	memset(pos,0,sizeof(pos));
	int distance=0,L,S,T,M,oldlen,ziplen,i,j; //distance=压缩后每个石子的距离,oldlen=压缩前的石子距离,ziplen=压缩后的石子距离
	scanf("%d%d%d%d",&L,&S,&T,&M);
	for(i=1;i<=M;i++)
		scanf("%d",&stone[i]);
	sort(stone+1,stone+M+1); //将石子距起点长度升序排序
	for(i=1;i<=M;i++) //循环,压缩石子间的距离
	{
		oldlen=stone[i]-stone[i-1]; //压缩前的石子间距
		ziplen=T; //压缩后的石子间距=青蛙单次跳跃最大距离
		if(ziplen>oldlen) ziplen=oldlen; //如果压缩距离大于原始距离,压缩距离等于原始距离
		distance+=ziplen; //更新压缩后最后一个石子的位置
		pos[distance]=1; //新的最后一个石子的位置标记为1,有石子
	}
//	f[0]=1;
	for(i=1;i<=distance+T;i++) 
		//由于青蛙最后一次跳跃的落点位置可能大于压缩后总距离distance,但它一定小于distance+T
		//因此循环终点定为distance+T,i=当前青蛙的位置
	{
		int minn=INF; //到达i点经过的最少石子数
		for(j=i-T;j<=i-S;j++) //要想到达i,j最近要到i-T,最远到i-S
		{
			if(j>=0)
				minn=min(minn,f[j]); //更新到达i点要经过的最少石子数(i点的石子不算)
		}
		f[i]=minn+pos[i];
	}
	int ans=INF;
	for(i=distance;i<=distance+T;i++) //最终落点在区间[distance,distance+T],寻找最优解的落点
		ans=min(ans,f[i]);
	if(S==T) //如果最小跳跃距离等于最大跳跃距离,那么ans=距离是S(T)的倍数的石子数
	{
		ans=0;
		for(i=1;i<=M;i++)
		{
			if(stone[i]%S==0) //第i个石头的距离是S的倍数
				ans++;
		}
	}
	printf("%d\n",ans);
	return 0;
}