在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度)。坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数(包括S,T)。当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。
题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。
输入第一行有一个正整数L(1<=L<=109),表示独木桥的长度。第二行有三个正整数S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中1<=S<=T<=10,1<=M<=100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。
输出只包括一个整数,表示青蛙过河最少需要踩到的石子数。
10
2 35
2 3567
2
数据规模
对于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,如下图所示
第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; }