【问题描述】
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度)。坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数(包括S,T)。当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。
题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数
【输入文件】
输入文件river.in的第一行有一个正整数L(1 <= L <= 109),表示独木桥的长度。第二行有三个正整数S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中1 <= S <= T <= 10,1 <= M <= 100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。
【输出文件】
输出文件river.out只包括一个整数,表示青蛙过河最少需要踩到的石子数。
【样例输入】
10
2 3 5
2 3 5 6 7
【样例输出】
2
【数据规模】
对于30%的数据,L <= 10000;
对于全部的数据,L <= 10^9。
分析:
动态规划,需要状态压缩。
状态:F[i] 跳到距离i处碰到的最少的石子数。
状态转移方程:F[i]=min{ F[i-k] } + F[i] (S<=k<=T i-k>=0)
边界条件:F[i]=1 i处有石子
目标结果:min{ F[k] } (L+1<=k<=L+T-1)
状态压缩
由于L最大为10^9,直接线性递推会超时。发现石子数很少,这意味着路径上有许多很长的空白段。我们可以把长度大于S*T的空白段都缩小到S*T,这样最长只有10090了。
【参考程序】:
#include<cstring>
#include<cstdio>
#include<iostream>
#define maxint 0xFFFFFFF;
using namespace std;
int F[10100],stone[110];
int n,m,s,t,ans;
int cmp(const void *s,const void *t)
{
int i=*(int *)s,j=*(int *)t;
return i-j;
}
void reduce()
{
int p=s*t,k;
stone[0]=0; stone[m+1]=n;
memset(F,0,sizeof(F));
for (int i=1;i<=m+1;i++)
{
if (stone[i]-stone[i-1]>p)
{
k=stone[i]-stone[i-1]-p;
for (int j=i;j<=m+1;j++)
stone[j]-=k;
stone[i]=stone[i-1]+p;
}
F[stone[i]]=1;
}
n=stone[m+1];
}
void dp()
{
int mins;
for (int i=1;i<=n+t-1;i++)
{
mins=maxint;
for (int j=s;j<=t;j++)
if (i-j>=0 && F[i-j]<mins)
mins=F[i-j];
F[i]+=mins;
}
}
int main()
{
scanf("%d",&n);
scanf("%d%d%d",&s,&t,&m);
for (int i=1;i<=m;i++) scanf("%d",&stone[i]);
if (s==t)
{
ans=0;
for (int i=1;i<=m;i++)
if (stone[i]%s==0) ans++;
printf("%d\n",ans);
exit(0);
}
qsort(stone+1,m,sizeof(int),cmp);
reduce();
dp();
ans=maxint;
for (int i=n+1;i<=n+t-1;i++)
if (ans>F[i]) ans=F[i];
printf("%d\n",ans);
return 0;
}