解题思路
离散化+动态规划
从简单方面入手,单纯考虑动态规划来说很容易找到状态转移方程。
dp[i]代表来到位置i的最少石子数。那么:
- 如果这个地方有石子 dp[i]=min(dp[i],dp[i - j]+1) j 在[s , t]中。
- 如果这个地方没有石子 dp[i]= min(dp[i],dp[i-j]) j 在[s , t]中。
这样大方向是对的,看到题目数据1e9……这长度也不能开下数组吖!那怎么办,看下总的个数和每步距离比较小,考虑离散。
我们知道假设 s = 4, t = 9。那么在 36 = 4 * 6之后的位置,可以选择一种方式让中途的某一次跳跃或者多个跳跃位置多跳一点点,抵达之后的位置,如果两个石子相距38来看就是中间一步跳4的我只要跳6就能稳定经过38。
这样就不难发现在 s × t 的之后位置,是一定可以到达的,那么我们就把之后大于 s × t的位置记作前一次到的加上s × t。(具体可看code
再把终点设置成为最后一个石子必到的地方加上 s × t,去递推状态。
中途遇到的必定经过的点状态转移方程与上文类似。
最终答案在刚好抵达终点和跳过终点之后更新最小值即可。
#include <bits/stdc++.h> #pragma GCC optimize(2) #pragma GCC optimize(3) using namespace std; #define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0) typedef long long ll; inline int read() { int s = 0, w = 1; char ch = getchar(); while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); } while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * w; } const int N = 105; const int M = 90 * 105; int a[N], b[N]; int dp[M], vis[M]; int main() { int l = read(); int s = read(), t = read(), m = read(); int base = s * t; for (int i = 1; i <= m; ++i) a[i] = read(); sort(a + 1, a + 1 + m); if (s == t) { int cnt = 0; for (int i = 1; i <= m; ++i) if (a[i] % s == 0) ++cnt; printf("%d\n", cnt); return 0; } for (int i = 1; i <= m; ++i) { int d = a[i] - a[i - 1]; if (d > base) d = base; b[i] = b[i - 1] + d; vis[b[i]] = 1; } memset(dp, 0x3f, sizeof(dp)); dp[0] = 0; l = b[m] + base; //终点设置为最后一个石子的再加base位置 for (int i = 1; i <= l; ++i) for (int j = s; j <= t; ++j) if (i - j >= 0) { if (vis[i]) dp[i] = min(dp[i], dp[i - j] + 1); else dp[i] = min(dp[i], dp[i - j]); } int ans = 0x3f3f3f3f; for (int i = b[m]; i <= l; ++i) ans = min(ans, dp[i]); //在可以一步跳过终点或者刚好终点的位置取最小 printf("%d\n", ans); return 0; }