弦月的博客
锦瑟无端五十弦,一弦一柱思华年。

描述

在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点: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 <iostream>
#include <algorithm>
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;
}

参考

posted on 2017-06-01 13:18  弦月C  阅读(251)  评论(0编辑  收藏  举报