[NOIP 2005 T2] 过河 (动态规划+简单数论)

题目大意:一只青蛙要从数轴原点向右跳过L(L<=10^9)的距离,在(0,L)上存在m个位于整点位置的石头(m<=100),青蛙每次跳跃可以向右跳[s,t](1<=s<=t<=10)间的一个整数距离。请问它最少踩到几块石头?

思路:

       本题容易从动态规划的角度去思考,用\small f[i]表示跳到位置i时最少踩了几个石头,直接枚举向右跳多远更新即可。但是L太大了,铁定超时超空间。

       不过我们发现这题的m却是出奇的小,说明数轴上的石头其实非常稀疏。我们稍加观察就可以发现,大量的转移是没有意义的,因为如果两个石头\small a[i]\small a[j]间的距离过大,很快\small f数组会出现一个分界点\small x,满足\small \dpi{100} \small f[x]..f[a[j]-1]清一色等于一个相同的值。实际上,我们只要保证\small f[a[j]-t]..f[a[j]-1]依然清一色就可以了,前面那么多一样的位置完全可以去掉,因为他们和\small a[j]之后的转移没有半毛钱关系,去掉并不会影响答案。

      那么问题来了,我们怎么知道这个分界点是在哪里呢?究竟距离达到什么程度才可以压缩?如果你赶时间,我可以直接告诉你结论:令\small Lim=110,只要两块石头的距离超过这个值,就直接将距离变为这个值,这样的压缩是没有问题的,然后再用一开始的方法动态规划即可在很短的时间内求出答案。

     下面就是相关证明了:

      我们考虑一个点\small x,从它向右跳,可以达到哪些点呢?假设在\small k步以内,我们可以分类讨论:

      跳1次可以到达:\small [x+s,x+t]

      跳2次可以到达:\small [x+2s,x+2t]

      ......

      跳\small k次可以到达:\small [x+ks,x+kt]

      上面这\small k个区间的并就是可以到达的点的集合。

      可以发现,\small k比较小的时候,这些区间之间存在间隙,但是当\small k大到一定程度,第\small k个区间和第\small k-1个区间之间就能实现无缝衔接,这就意味着,从无缝衔接开始往后,所有位置都可以到达。

      举个例子:x=0,s=3,t=4,那么区间分别为[3,4],[6,8],[9,12],[12,16]……可以发现除了1,2,5不可以到达以外,其它位置都可以到达,后面的区间都会完美覆盖数轴,不会漏出空隙。

    我们只要求出最早出现完美覆盖的\small k,就可发现压缩距离的关键所在。

    也就是求最小的\small k,满足\small x+(k-1)t>=x+ks-1,求得\small k>=\frac{t-1}{t-s},根据题目给出的数据范围,右边的式子不可能超过9,也就是说无论如何,9步之后所有的位置都可以到达。

    我们再来看\small f数组,很显然如果后面没有石头的干扰,f数组会逐渐变成清一色的值,具体地,由于\small [x+1,x+t]中的每个位置都可以到达\small x+10t向后的任意位置,所以\small f[10t],f[10t+1]...都会等于\small min(f[x+1],...,f[x+t])。我们令\small Lim=11t,即可保证压缩后,对下一个石头的转移没有影响(因为它前面\small t个位置依然都是清一色的值)。也就是说\small Lim>=110时可以保证这样的压缩万无一失。为了保险,我的程序设置的是125。

    这题还有一个坑,那就是s=t的情况,这个时候不能用上面的分析,因为\small k>=\frac{t-1}{t-s}这个式子的分母会变成0。这个时候注意特判掉,直接看有多少个石头的位置是t的倍数即可。

    说了那么多,其实想起来挺快的,也就几分钟的事情,写起来也很方便,具体看代码吧(这个代码在洛谷上AC了,不过如果读者发现其它什么错误还请留言哈)。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define rep(i,j,k) for (i=j;i<=k;i++)
using namespace std;
const int Lim=125,N=100;
int s,t,m,n,i,j,ans,L,a[N+5],b[N+5],f[N*Lim+50],stone[N*Lim+50];
void updata(int &x,int y) { if (x==-1) x=y; else x=min(x,y); }
int main()
{
	scanf("%d",&L);
	scanf("%d%d%d",&s,&t,&m);
	rep(i,1,m) scanf("%d",&a[i]);
	sort(a+1,a+1+m);
	if (s==t)
	{
		rep(i,1,m) if (a[i]%t==0) ans++;
		printf("%d\n",ans);
		return 0;
	}
	rep(i,1,m) b[i]=min(a[i]-a[i-1],Lim);
	rep(i,1,m) a[i]=a[i-1]+b[i],stone[a[i]]=1;
	memset(f,-1,sizeof(f));
	f[0]=0; n=a[m]+t; ans=m+1;
	rep(i,0,n)
		if (f[i]!=-1)
			rep(j,s,t)
				updata(f[i+j],f[i]+stone[i+j]);
	rep(i,a[m]+1,n)
		if (f[i]!=-1) ans=min(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值