「解题报告」LOJ561 「LibreOJ Round #9」CommonAnts 的调和数

发布时间 2023-05-24 17:54:19作者: APJifengc

模拟赛考的题,但是模拟赛没有打,哈哈,摆烂。

考场上想到大致做法了,没继续推,去打 GP of Tokyo 了。


首先发现操作都在查询前面,所以我们只需要预处理出答案即可。

我们先记 \(b_i\) 表示对 \(i\) 进行的操作的总和,那么容易写出 \(a_i\) 的式子:

\[a_i = \sum_{j | i} b_j \cdot \frac{i}{j} \]

然后考虑查询操作,也可以写出式子:

\[c_i = \sum_{i | j} a_j \cdot \frac{j}{i} \]

发现两个操作都是狄利克雷前缀和 / 后缀和的形式,于是直接暴力计算就能做到 \(O(n \log n) / O(n \log \log n)\)

考虑题目中给出的限制:所有的操作下标的总质因子个数不超过 \(10\) 个。拿 \(\{2, 3, 5, 7, 11, 13, 17, 19, 23, 29\}\) 打个表,发现只出现这些数为质因子的不超过 \(10^9\) 的数仅有约 \(2 \times 10^5\) 个。这个性质是很优秀的,因为这告诉我们 \(a_i, c_i\) 中有用的数只有 \(2 \times 10^5\) 个。我们记这个数量为 \(V\)

考虑直接把两个式子揉到一起:

\[\begin{aligned} c_i &= \sum_{i | j} \sum_{k | j} b_k \frac{j}{k} \frac{j}{i}\\ &= \frac{1}{i} \sum_{i | j} j^2 \sum_{k | j} \frac{b_k}{k}\\ \end{aligned} \]

看起来直接做就行了,但是发现问题出在枚举的倍数 \(j\) 上,这个数是可以达到 \(10^9\) 的量级的。但是观察后面的式子,我们枚举的是 \(j\) 的因子,而 \(b_k\) 仅有在 \(k\) 只存在前面的 \(10\) 个质因子的时候有值,这意味着 \(j\) 除了前面的 \(10\) 个质因子之外的因子是对答案不造成任何贡献的。而又因为 \(j^2\) 完全积性的性质,我们可以先枚举所有只存在 \(10\) 个质因子的数,然后再枚举所有不包含 \(10\) 个质因子的数,这样就能把 \(j\) 的贡献拆开了。

具体的,我们设 \(f(n) = \sum_{i = 1}^n i^2 [\gcd(i, p_1 p_2 \cdots p_{10}) = 1]\)\(g_i = \sum_{j | i} \frac{b_j}{j}\)\(S\)\([1, n]\) 中仅存在 \(10\) 个质因子的数的集合,那么上式可以化为:

\[c_i = \frac{1}{i} \sum_{i | j, j \in S} f\left(\left\lfloor\frac{n}{j}\right\rfloor\right)j^2 g_j\\ \]

\(g_j\) 可以直接狄利克雷前缀和计算出来,外层也可以直接狄利克雷后置和计算,现在只需要计算出 \(f(n)\) 即可。注意到 \(f(n)\) 仅会取到 \(O(\sqrt{n})\) 个取值,所以考虑直接预处理出来所有值。

这部分好像做法很多,有一个很暴力的做法就是考虑容斥,\(2^{10}\) 枚举 \(i\) 一定出现过某些质因子,然后计算一下 \(i^2\) 的前缀和即可。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005, P = 998244353;
int n, m, q;
int p[MAXN], x[MAXN], k[MAXN];
vector<int> fac, s;
int qpow(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1) ans = 1ll * ans * a % P;
        a = 1ll * a * a % P;
        b >>= 1;
    }
    return ans;
}
const int INV6 = qpow(6, P - 2);
void dfs(int i, int x) {
    if (i == fac.size())
        s.push_back(x);
    else {
        for (long long k = 1; x * k <= n; k *= fac[i]) {
            dfs(i + 1, x * k);
        }
    }
}
unordered_map<int, int> a, b, c;
int f(int x) {
    x %= P;
    return 1ll * x * (x + 1) % P * (2 * x + 1) % P * INV6 % P;
}
int main() {
    scanf("%d%d%d", &n, &m, &q);
    auto factor = [&](int x) {
        for (int i : fac) {
            while (x % i == 0) x /= i;
        }
        if (x != 1) {
            for (int i = 2; i * i <= x; i++) {
                if (x % i == 0) {
                    fac.push_back(i);
                    while (x % i == 0) x /= i;
                }
            }
            if (x != 1) {
                fac.push_back(x);
            }
        }
    };
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &p[i], &x[i]);
        factor(p[i]);
    }
    for (int i = 1; i <= q; i++) {
        scanf("%d", &k[i]);
        factor(k[i]);
    }
    dfs(0, 1);
    for (int i = 1; i <= m; i++) {
        a[p[i]] = (a[p[i]] + x[i]) % P;
    }
    sort(fac.begin(), fac.end());
    sort(s.begin(), s.end());
    for (int x : s) {
        a[x] = 1ll * a[x] * qpow(x, P - 2) % P;
    }
    for (int pri : fac) {
        for (int x : s) if (1ll * x * pri <= n) {
            a[x * pri] = (a[x * pri] + a[x]) % P;
        }
    }
    for (int x : s) {
        int m = n / x;
        if (b.count(m)) continue;
        for (int t = 0; t < (1 << fac.size()); t++) {
            int mul = 1;
            for (int i = 0; i < fac.size(); i++) if (t >> i & 1) {
                if (1ll * mul * fac[i] > m) {
                    mul = 0;
                    break;
                }
                mul *= fac[i];
            }
            if (!mul) continue;
            b[m] = (b[m] + 1ll * ((__builtin_popcount(t) & 1) ? P - 1ll : 1ll) * mul % P * mul % P * f(m / mul)) % P;
        }
    }
    reverse(s.begin(), s.end());
    for (int x : s) {
        c[x] = 1ll * b[n / x] * x % P * x % P * a[x] % P;
    }
    for (int pri : fac) {
        for (int x : s) if (x % pri == 0) {
            c[x / pri] = (c[x / pri] + c[x]) % P;
        }
    }
    for (int x : s) {
        c[x] = 1ll * c[x] * qpow(x, P - 2) % P;
    }
    for (int i = 1; i <= q; i++) {
        printf("%d\n", c[k[i]]);
    }
    return 0;
}