题解:P10207 [JOI 2024 Final] 马拉松比赛 2

· · 题解

MX-C 8.16 模拟赛 T4。

对楼上大佬的题解做的一些详细解释。

题意

一条数轴上放了 n 个球,第 i 个球放在了位置 x_i,一个位置可能有多个球。

你需要从起点 s 出发,捡起所有 n 个球(一个球捡起了就不能放下了),回到终点 t。如果你能在规定时间 T 内完成,则算你成功。

已知:拿起 1 个球需要 1 单位时间;带着 x 个球移动 1 个单位距离需要 x + 1 单位时间。

给定 n,l,x_i,你需要回答 q 个询问,每个询问给定 s,t,T,你需要回答能不能成功。

所有数都 \le 5 \times 10^5

做法

显然第一步是将球按照坐标排序。

我们考虑如果在 t 的某侧有两个球,那么最优策略一定是选捡那个远的再捡近的。这是因为如果先捡近的你会抱着这个球跑一个来回。

所以对于 t 的某侧(左侧或右侧)而言,一定是先捡最远的,再捡第二远的,以此类推。所以我们捡的第一个球一定是第一个或第 n 个,这是因为我们将球按坐标排序了。

考虑最朴素的区间 DP。设 f(l, r, 0/1) 表示我们已经捡了 [1, l-1][r+1, n] 这些球,且现在我们正位于第 l/r 个球的位置,所需要的最小时间。这样设状态的问题是没法设置边界,因此我们再引入一个与 f 意义相同的 g,其中 f 表示起点是第一个球,g 表示起点是第 n 个球。

显然边界 f(1,n,0) = 0,g(1,n,1)=0。转移枚举一下最后一个捡的球是 l - 1 还是 r + 1

f(l,r,0) = \min \{f(l-1,r,0) + (cnt+1)\times (x_l - x_{l - 1}), f(l, r+1, 1) + (cnt+1)\times (x_{r+1}-x_l)\} \\ f(l,r,1) = \min \{f(l-1,r,0)+(cnt+1)\times(x_r - x_{l-1}), f(l,r+1,1)+(cnt+1)\times(x_{r+1}-x_r)\} 其中 $cnt = (l-1) + (n-r)$,表示 $[1,l-1] \cup [r+1,n]$ 的大小,即当前在手上的球的数量。 考虑计算答案。对于 $t$ 的一侧而言,因为我们按照球到 $t$ 的距离从大到小取球,所以最后一个球一定距离 $t$ 最近。分别求出这两侧的这两个最近的球 $p_1,p_2$。那么我们可以枚举第一个取的球(第 $1$ 个或第 $n$ 个),以及最后一个取的球(第 $p_1$ 个或第 $p_2$ 个),即: $$ \min \{|s-x_1| + f(p,p,0) + (n + 1) \times |x_p-t|, |s-x_n| + f(p,p,0) + (n+1) \times |x_p - t|\} $$ 其中 $p \in \{p_1,p_2\}$,我们可以枚举。 复杂度?$n^2$ 过五十万你搞笑呢? 令 $S$ 表示将球的坐标去重后的数量。**注意到**在最坏情况下,如果我们在第一米捡第一个球,第二米捡第二个球,以此类推,那么总答案为 $2+3+\dots+(S+1) = \mathcal O(S^2)$。而 $t \le 5 \times 10^5$ 远小于这个数,所以当 $S$ 大于某个阈值后全输出 $\texttt{No}$ 即可。这个阈值是 $10^3$。 那么区间 DP 的状态数优化到了 $10^3 \times 10^3 \times 2$。这是被允许的。 去重后实现较为复杂。一个记忆化搜索的实现如下: ```cpp #include <bits/stdc++.h> using namespace std; #define int long long const int N = 1e6 + 10, INF = 1e9; int n, L, x[N]; int s[N]; int f[1010][1010][2]; int g[1010][1010][2]; int dp0(int l, int r, bool flg) { if (l == 1 && r == n) return flg ? INF : 0; if (~f[l][r][flg]) return f[l][r][flg]; int res = 1e9; int sum = 1 + (x[l] ? s[x[l] - 1] : 0) + s[L] - s[x[r]]; if (!flg) { if (l > 1) res = min(res, dp0(l - 1, r, 0) + sum * (x[l] - x[l - 1])); if (r < n) res = min(res, dp0(l, r + 1, 1) + sum * (x[r + 1] - x[l])); } else { if (l > 1) res = min(res, dp0(l - 1, r, 0) + sum * (x[r] - x[l - 1])); if (r < n) res = min(res, dp0(l, r + 1, 1) + sum * (x[r + 1] - x[r])); } return f[l][r][flg] = res; } int dp1(int l, int r, bool flg) { if (l == 1 && r == n) return !flg ? INF : 0; if (~g[l][r][flg]) return g[l][r][flg]; int res = 1e9; int sum = 1 + (x[l] ? s[x[l] - 1] : 0) + s[L] - s[x[r]]; if (!flg) { if (l > 1) res = min(res, dp1(l - 1, r, 0) + sum * (x[l] - x[l - 1])); if (r < n) res = min(res, dp1(l, r + 1, 1) + sum * (x[r + 1] - x[l])); } else { if (l > 1) res = min(res, dp1(l - 1, r, 0) + sum * (x[r] - x[l - 1])); if (r < n) res = min(res, dp1(l, r + 1, 1) + sum * (x[r + 1] - x[r])); } return g[l][r][flg] = res; } signed main() { cin >> n >> L; int backup = n; for (int i = 1; i <= n; ++ i ) { cin >> x[i]; s[x[i]] ++ ; } for (int i = 1; i <= L; ++ i ) { s[i] += s[i - 1]; } sort(x + 1, x + n + 1); n = unique(x + 1, x + n + 1) - x - 1; memset(f, -1, sizeof f); memset(g, -1, sizeof g); int q; cin >> q; while (q -- ) { if (n > 1e3) { puts("No"); continue; } int s, t, jp; cin >> s >> t >> jp; int res = 1e9; if (x[n] >= t) { int p = lower_bound(x + 1, x + n + 1, t) - x; res = min(res, abs(s - x[1]) + dp0(p, p, 0) + (backup + 1) * abs(x[p] - t)); res = min(res, abs(s - x[n]) + dp1(p, p, 0) + (backup + 1) * abs(x[p] - t)); } if (x[1] < t) { int p = lower_bound(x + 1, x + n + 1, t) - x - 1; res = min(res, abs(s - x[1]) + dp0(p, p, 0) + (backup + 1) * abs(x[p] - t)); res = min(res, abs(s - x[n]) + dp1(p, p, 0) + (backup + 1) * abs(x[p] - t)); } res += backup; puts(res <= jp ? "Yes" : "No"); } return 0; } ```