P1052 过河


知识点: DP

原题面


题意简述

给定一数轴,标号 \(0\sim L\)
\(0\) 出发,每次可向右移动 \(S\sim T\) 个单位。
数轴上有 \(M\) 个点,求移动到数轴外,经过的最少的点数。
\(1\le M\le 100,\ 1 \le L\le 10^9, 1\le S\le T\le 10\)


分析题意

算法一

我会暴力!
\(f_i\) 表示,到达 \(i\) 时,经过的最少的点数。
\(a_i\) 表示位置为 \(i\) 的点的数量,则有:

\[f_i = \min_{j=i-T}^{i-S}\{f_j + a_i\} \]

\(S,T\) 较小,可看做常数,复杂度 \(O(L)\) 级别。
期望得分 \(30\text{pts}\)


算法二

发现 \(M \le 100\),但 \(L\le 10^9\)
数轴上有大段的空白区域没有点存在,这些位置对答案无贡献。
而算法一中仍然在这些位置进行了转移,浪费了大量时间。

\(S < T\) 时:
发现有很多移动距离,都可以凑出来。
这个问题类似 Noip2017 D1T1 小凯的疑惑。
\(S=5, T=6\) 时,最大的凑不出来的距离为 \(19\),比 \(19\) 更大的距离均可凑出来。

考虑两个距离过大的点。
显然,从左边的点出发,最大的凑不出来的距离 右侧的位置(包括右侧的点),均可通过凑距离到达。

而两个点之间的位置 对答案无贡献。
在不影响 可到达性的同时,可考虑将两点距离缩小。
缩小到仍出现 上述任意点可到达的情况,不影响答案。
以下有两种缩法:

  1. \(2520\) 缩,由于 \(\operatorname{lcm}(1\sim 10) = 2520\),距离为 \(2520\) 时,必然出现上述情况。
  2. \(71\) 缩, \(9,10\) 凑不出来的最大距离为 \(9 \times 10 - 9 - 10 = 71\),则距离 \(71\) 是出现上述情况的最小值。

题解中神仙们给了缩距离的详细证明 Luogu题解。

复杂度 \(O(kM)\) (\(k\) 为缩成的距离),期望得分 \(100\text{pts}\)


WA 成 80 了。

发现算法二并不适用于 \(S=T\) 的情况。
\(S=T\) 时,移动路径已知,先后经过 \(S,2S,...,kS\) 这些点。
则 位置为 \(S\) 的倍数的点的数量 即为答案。


代码实现

写的时候根本没考虑应该缩成什么距离= =
想着大就完事了,直接缩成 10000 了。

//知识点:DP
/*
By:Luckyblock
*/
#include 
#include 
#include 
#include 
#define ll long long
const int kMaxm = 110;
const int kMaxDis = 10000;
const int kMaxn = 110 * kMaxDis;
//=============================================================
int L, S, T, M, pos[kMaxm];
int a[kMaxn], f[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
//=============================================================
int main() {
  L = read(), S = read(), T = read(), M = read();
  for (int i = 1; i <= M; ++ i) pos[i] = read();
  std :: sort(pos + 1, pos + M + 1);
  if (S == T) {
    int ans = 0;
    for (int i = 1; i <= M; ++ i ) ans += pos[i] % S == 0;
    printf("%d\n", ans);
    return 0;
  }

  L = 0;
  for (int i = 1; i <= M; ++ i) {
    int dis = pos[i] - pos[i - 1];
    if (pos[i] - pos[i - 1] > kMaxDis) dis = kMaxDis;
    a[(L = L + dis)] ++;
  }
  L += kMaxDis;

  memset(f, 63, sizeof(f));
  f[0] = 0;
  for (int i = 1; i <= L; ++ i) {
    for (int j = std :: max(i - T, 0); j <= i - S; ++ j) {
      f[i] = std :: min(f[i], f[j] + a[i]);
    }
  }
  printf("%d\n", f[L]);
  return 0;
}