「NOIP 2005」过河

题目大意:在一个长为 $L$ 的坐标轴上,有 $M$ 个分布不均的石子,有一只青蛙要从 $0$ 跳到 $L$,每次跳跃距离的范围是 $[S,T]$,问最少踩到多少枚石子?$S,T \leq 10, M \leq 100, L \leq 10^9$

由于 $M$ 很小,可以进行路径压缩。

根据「NOIP 2017」小凯的疑惑可知,如果 $n \perp m$,则最大的不能由 $n$ 和 $m$ 累加来表示的数为 $n \times m-(n+m)$,于是我们可以用 $t$ 和 $t-1$ 来转移,下图为 $t=4$ 的情况:

我们可以越过 $t(t-1)-(t+t-1)$ 的区域到达一个每个位置都可及的空位区($t-1$),通过这个空位区可以转移到左边的每一个可以一步跳到的石子。

如果两石子间的距离大于 $t-1+t(t-1)-t-t+1=(t-2)t$,把它变为 $(t-2)t$ 即可。

实现需要注意以下几点:

  • 注意特判 $s=t$ 的情况
  • $t=2$ 时 $(t-2)t=0$,但应该把间距控制成 $1$ 而不是 $0$,因为中间一定要留一个空位区
  • 不要忘记排序
  • 到达终点或越过终点都可以
#include <cstdio>
#include <algorithm>
#include <cstring>

int a[105], b[100005], f[100005];

int read() {
    int x = 0; char c = getchar();
    while (c < '0' || c > '9') c = getchar();
    while (c >= '0' && c <= '9') {
        x = (x << 3) + (x << 1) + (c ^ 48);
        c = getchar();
    }
    return x;
}
int min(int x, int y) {
    return x < y ? x : y;
}

int main() {
    int l = read(), s = read(), t = read(), m = read();
    for (int i = 1; i <= m; ++i) a[i] = read();
    if (s == t) {
        int ans = 0;
        for (int i = 1; i <= m; ++i)
            if (a[i] % t == 0) ++ans;
        printf("%d\n", ans);
    } else {
        int n = 0, lim = t * (t - 2);
        if (lim == 0) ++lim; //注意特判t=2的情况, 中间一定要留一个空位
        std::sort(a + 1, a + m + 1); //不要忘记排序
        for (int i = 1; i <= m; ++i) {
            int d = a[i] - a[i-1] - 1;
            if (d > lim) d = lim;
            n += d + 1, b[n] = 1;
        }
        if (l - a[m] - 1 > lim) l = n + lim + 1;
        else l = n + l - a[m];
        memset(f, 0x3f, sizeof f);
        f[0] = 0;
        for (int i = s; i <= l; ++i) {
            for (int j = i - t; j <= i - s; ++j) {
                if (j < 0) continue;
                f[i] = min(f[i], f[j]);
            }
            if (b[i]) ++f[i];
        }
        for (int i = l - t; i <= l; ++i) { //可以越过终点
            if (i < 0) continue;
            f[l] = min(f[l], f[i]);
        }
        printf("%d\n", f[l]);
    }
    return 0;
}

时间复杂度 $O(n \log n)$