CF1845F Swimmers in the Pool 记录
题目链接:https://codeforces.com/contest/1845/problem/F
题意简述
有 \(n\) 个速度 \(v_i\) 各不相同的运动员在一个长为 \(l\) 的游泳池内游泳,每次到边沿立即掉头。求在 \(t\) 时间及以内,至少两个运动员相遇的时刻的个数。
$ 1 \le l, t \le 2 \times 10^9, 2 \le n \le 2 \times 10^5, 1 \le v_i \le 2 \times 10^5$。
解法(官解)
绝对是很好,很有教育意义的一题。
首先,根据中学物理知识,很容易知道速度分别为 \(v_i\) 和 \(v_j\) 的运动员在何时相遇:$ 2kl = (v_i + v_j) \cdot t $ 或 \(2kl = |v_i - v_j| \cdot t\),\(k \in \mathbb {N^+}\)。
计算\(|v_i - v_j|\) 和 \(v_i + v_j\) 可能的取值集合 \(W\),则可行的时刻有 \(k \cdot \dfrac{2l}{w} (k \le \dfrac{wt}{2l})\) 的形式。可行的取值集合可以通过 FFT 计算,注意排除自身与自身相乘的项(由于值域不是很大,可以采用 NTT 计算以加速)。
但注意到这样的仍然项存在重复。忽略公因式 \(2l\),\(\dfrac {k_1} {w_1} = \dfrac {k_2} {w_2}\) 的情况会被重复计算。防止重复计算的一种方式是只计算 \(k_1\) 与 \(w_1\) 互质的情况。这是因为对任意 \(d | w\),\(\dfrac{kd}{w} (kd \le \dfrac{wt}{2l})\) 完全与 \(\dfrac{k} {w/d} (k \le \dfrac{(w/d)t}{2l})\) 的时刻一一相等。因此,我们可以把不互质的计算“安全地”传给 \(w\) 的所有因子,只需将它们“激活”即可,而不用担心产生数多或数漏。
对于互质部分的计数,使用莫比乌斯反演可以解决:
代入 \(N = \dfrac{wt}{2l}\),也就是对每个被激活的 \(w\),将 \(\sum\limits_{d | w} \mu(d) \lfloor \dfrac {wt} {2ld} \rfloor\) 加入到答案即可。
FFT 和枚举因数的时间复杂度均为 \(O(M\log M)\),因此总时间复杂度为 \(O(n + M\log M)\)。
代码实现(C++,使用 acl)
#include <bits/stdc++.h>
using namespace std;
#include <atcoder/convolution>
#include <atcoder/modint>
using i64 = int64_t;
using Z = atcoder::modint998244353;
using Z7 = atcoder::modint1000000007;
vector<int> get_mu(int n) {
vector<int> mu(n + 1), p;
vector<bool> flg(n + 1, 0);
mu[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!flg[i]) p.push_back(i), mu[i] = -1;
for (int j = 0; j < (int)p.size() && i * p[j] <= n; ++j) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
return mu;
}
vector<vector<int>> range_factor_get(int n) {
vector<vector<int>> ans(n + 1, vector<int>());
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n / i; j++) ans[i * j].push_back(i);
return ans;
}
constexpr int M = 200000 + 233;
auto mu = get_mu(2 * M);
auto fac = range_factor_get(2 * M);
void solve() {
int n, t, l;
cin >> l >> t >> n;
vector<Z> v(M + 1);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
v[x] = 1;
}
// 1. calc all possible v1+v2 and |v1-v2|
vector<int> w(2 * M + 1);
{
auto add = atcoder::convolution(v, v);
for (int i = 1; i <= 2 * M; i++) {
if (i % 2 == 0) add[i] -= v[i / 2];
w[i] |= (bool)(add[i].val());
}
auto rv = v;
ranges::reverse(rv);
auto sub = atcoder::convolution(v, rv);
for (int i = 0; i <= 2 * M; i++) w[abs(i - M)] |= (bool)sub[i].val();
}
// 2. calc answer
Z7 ans = 0;
for (int i = 2 * M; i >= 1; i--) {
if (w[i]) {
for (int d : fac[i]) {
ans += mu[d] * ((i64)t * i / (2 * l) / d);
w[i / d] = 1;
}
}
}
cout << ans.val() << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) { solve(); }
}