描述
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度)。坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数(包括S,T)。当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。
题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。
对于30%的数据,L <= 10000;
对于全部的数据,L <= 10^9。
格式
输入格式
输入的第一行有一个正整数L(1 <= L <= 10^9),表示独木桥的长度。第二行有三个正整数S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中1 <= S <= T <= 10,1 <= M <= 100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。
输出格式
输出只包括一个整数,表示青蛙过河最少需要踩到的石子数。
样例1
样例输入1
10
2 3 5
2 3 5 6 7
样例输出1
2
限制
1s
来源
NOIp2005 第二题
题解
根据题目意思可以发现这是一道动态规划的问题。并且我们可以很快地列出他的状态转移方程如下:
f[i] = min{f[i-j]}+a[i], (s<=j<=t)
其中:
f[i]表示到达第i点位置最少要踩到的石头数;
a[i]表示第i点位置是否有石子,有则a[i]为1,否则a[i]为0。
但是这样的话时间复杂度达到了O(L*T),会超时。
这个时候我们回去考虑是否能够压缩一下数据,比如说L的大小。
我们需要用到下面这个定理:
对于公式
P*x+(P+1)*y=Q
其中x,y是未知数,P是跳跃的距离,P+1也是跳跃的距离,对于任意Q,Q>=P(P-1), x,y一定存在正整数解,换句话说当两个石子之间的距离大于等于T(T-1)时,中间有相当大的距离是每个点都可以跳到的,因为没有石子,所以对答案没有贡献,可以取模(%90)
这样我们就成功的把很多没有用到的状态压缩了。这就是所谓的状态压缩DP。
优化后的代码如下:
#include
#include
using namespace std;
#define inf (1<<29)
int l, s, t, m, f[10010], a[10010], p[110];
int main()
{
cin >> l >> s >> t >> m;
for (int i = 0; i < m; i ++)
cin >> p[i];
if (s == t)
{
int res = 0;
for (int i = 0; i < m; i ++)
if (p[i] % s == 0)
res ++;
cout << res << endl;
return 0;
}
sort(p, p + m);
p[m] = l;
p[0] %= 90;
for (int i = 1; i <= m; i ++)
p[i] = p[i-1] + (p[i] - p[i-1]) % 90;
for (int i = 0; i < m; i ++)
a[p[i]] = 1;
f[0] = 0;
for (int i = 1; i <= p[m]; i ++)
f[i] = inf;
for (int i = 1; i <= p[m]; i ++)
for (int j = s; j <= t; j ++)
if (i - j >= 0)
f[i] = min(f[i], f[i-j]+a[i]);
cout << f[p[m]] << endl;
return 0;
}
参考