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\) 的所有因子,只需将它们“激活”即可,而不用担心产生数多或数漏。

对于互质部分的计数,使用莫比乌斯反演可以解决:

\[\begin{aligned} \sum\limits_{k = 1}^{N} [\gcd(k, w) = 1] &= \sum\limits_{k = 1}^{N} \sum\limits_{d | k, d |w} \mu(d) \\ &= \sum\limits_{d | w} \mu(d) \sum\limits_{k = 1}^{N} [d | k] \\ &= \sum\limits_{d | w} \mu(d) \lfloor \dfrac N d \rfloor \\ \end{aligned} \]

代入 \(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(); }
}
posted @ 2023-12-27 18:52  cccpchenpi  阅读(26)  评论(0编辑  收藏  举报