题解:CF2091G Gleb and Boating

· · 题解

题意

你需要在 [0,s] 的数轴上移动。不能超出这个范围。初始步长是 k。初始默认方向是 0\to s。即 p 走一步走到 p+k。转弯一次会令 k\gets \max\{k-1,1\}。转弯一次是从 p 走一步走到 p-k,以此类推。

问到达 s 时最后的 k 最大是几。

多测,1\leq t\leq 1001\leq s\leq 10^91\leq k\leq 10001\leq \sum k\leq 2000

做法

考虑直接走到 $k\lfloor s/k \rfloor$ 的位置。即最后一个 $k$ 的倍数。此时剩下的距离设为 $r=s\bmod k$。 $r=0$ 直接判掉答案为 $k$。 对于 $r\neq 0$ 的情况分讨如下。 若 $r\lt k-2$,考虑到我们往后一步往前一步相当于后退一步,往后 $p$ 步往前 $p$ 步就是后退 $p$ 步。我们总能找到一个 $p$ 满足 $r+p=k-2$,且由于 $s\geq k^2$ 所以 $p(k-1)\leq k\lfloor s/k \rfloor\lt k^2$。 若 $r=k-2$,我们往后退 $k-2$ 步然后往前就是一个解。显然有 $k-2 \mid (k-2)(k-1)+(k-2)$。 若 $r=k-1$,我们往后退 $k-3$ 步然后往前就是一个解。显然有 $k-2 \mid (k-3)(k-1)+(k-1)$。 所以 $s\geq k^2$ 总能找到 $k$ 或 $k-2$ 作为答案。 若 $s\lt k^2$ 则在数据范围下有 $s\lt 10^6$。而我们有显然的状态设计 $f_{i,j}$ 表示步长为 $i$ 时是否可以走到 $j$。转移就枚举走几个 $i$,直接做难以通过。 但是可以直接 bitset 优化。其实跑得很快。 --- @[wuxigk](https://www.luogu.com.cn/user/287029) 在这篇题解底下的回复(经过格式修复): >复杂度算错了吧,外层调和级数是 $s+s/2+\dots +s/k$,乘上单次 bitset 的 $s/w$,是 $s^2\log k/w$,正确的 bitset 优化应该是移 1 次 2 次 4 次 8 次这样吧。 我先阐述一下为什么我这个 DP 跑的很快。 考虑到 $s\geq k^2$ 的时候答案是不小于 $k-2$,所以答案是 $\sqrt s$ 量级的。 这个 DP 实际运算量是 $\mathcal O(\sum\limits_{\sqrt s}^k s^2/i/w)$ 的,打个表跑一下其实大概是 $2\times 10^{10}/w$ 的,可以轻松通过。 wuxigk 指出的做法,正如本文章代码部分所说,我尝试写了一个然后 WA 了一个很大的点,应该是这个做法下使用 `std::tr2::dynamic_bitset` 被叉了。我的这个 DP 能过只是因为本题答案的特性。如果真的想要学习正常的 bitset 优化,还是得写这个做法。另外这里也征求一份写了这个做法的代码 qaq。 --- 多测可以用 `std::tr2::dynamic_bitset`(这个东西大概是唯一一个 OI 赛场上能直接用的动态 bitset,但是有 bug,参考 <https://codeforces.com/blog/entry/129454>,但是这个题我本地拍了十万组没拍出来有锅),或者说开若干个大小为 $2$ 的幂的 bitset 每次选一个刚好够的用一下,或者直接手写一个。 # 代码 感谢 @[wuxigk](https://www.luogu.com.cn/user/287029) 指出的复杂度分析问题以及更优的 bitset 优化。但是我写挂了(流汗。WA 了一个很后的点,大抵是 `dynamic_bitset` 的 bug 在这种做法下体现出来被叉掉了。这份代码还是文中的暴力转移。 <https://codeforces.com/contest/2091/submission/312735736> ```cpp #include <bits/stdc++.h> #include <tr2/dynamic_bitset> using namespace std; using namespace tr2; void solve() { int s, k; cin >> s >> k; if (s >= k * k) return cout << (s % k ? max(1, k - 2) : k) << '\n', void(); vector<dynamic_bitset<>> f(k + 1); for (int i = 0; i <= k; i++) f[i].resize(s + 1); for (int i = 0; i <= s; i += k) f[k][i] = 1; for (int i = k; i >= 2; i--) { if (f[i][s]) return cout << i << '\n', void(); if (k - i & 1) { for (int j = 0; j <= s; j += i - 1) f[i - 1] |= (f[i] <<= i - 1); } else { for (int j = 0; j <= s; j += i - 1) f[i - 1] |= (f[i] >>= i - 1); } // cout << i - 1 << '\n'; // for (int j = 0; j <= s; j++) // cout << f[i - 1][j] << ' '; // cout << '\n'; } cout << 1 << '\n'; } int main() { int t; cin >> t; while (t--) solve(); return 0; } ```