洛谷P4240 毒瘤之神的考验 题解(莫比乌斯反演)

传送门

题意非常的简单:求 i = 1 n j = 1 m φ ( i j ) \sum\limits_{i=1}^n\sum\limits_{j=1}^m\varphi(ij) 998244353 998244353 取模

拿到这个式子,实在是没有忍住,一口气化了下去:

首先有 φ ( x y ) = φ ( x ) φ ( y ) ( ( x , y ) ) φ ( ( x , y ) ) \varphi(xy)=\frac{\varphi(x)\varphi(y)((x,y))}{\varphi((x,y))} ,其中 ( x , y ) (x,y) 表示 x x y y 的最大公约数。这个结论只要把欧拉函数写成 φ ( n ) = n ( 1 1 p i ) \varphi(n)=n\prod\left(1-\frac{1}{p_i}\right) 就比较显然了。

i = 1 n j = 1 m φ ( i j ) \sum\limits_{i=1}^n\sum\limits_{j=1}^m\varphi(ij)
= i = 1 n j = 1 m φ ( i ) φ ( j ) ( i , j ) φ ( ( i , j ) ) =\sum\limits_{i=1}^n\sum\limits_{j=1}^m\frac{\varphi(i)\varphi(j)(i,j)}{\varphi((i,j))}
枚举 g c d gcd
= d = 1 min ( n , m ) d φ ( d ) i = 1 n j = 1 m φ ( i ) φ ( j ) [ ( i , j ) = d ] =\sum\limits_{d=1}^{\min(n,m)}\frac{d}{\varphi(d)}\sum\limits_{i=1}^n\sum\limits_{j=1}^m\varphi(i)\varphi(j)[(i,j)=d]
= d = 1 min ( n , m ) d φ ( d ) i = 1 n d j = 1 m d φ ( i d ) φ ( j d ) [ ( i , j ) = 1 ] =\sum\limits_{d=1}^{\min(n,m)}\frac{d}{\varphi(d)}\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}\varphi(id)\varphi(jd)[(i,j)=1]
莫比乌斯反演
= d = 1 min ( n , m ) d φ ( d ) i = 1 n d j = 1 m d φ ( i d ) φ ( j d ) k ( i , j ) μ ( k ) =\sum\limits_{d=1}^{\min(n,m)}\frac{d}{\varphi(d)}\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}\varphi(id)\varphi(jd)\sum\limits_{k|(i,j)}\mu(k)
= d = 1 min ( n , m ) d φ ( d ) k = 1 min ( n d , m d ) μ ( k ) i = 1 n d k φ ( i d k ) j = 1 m d k φ ( j d k ) =\sum\limits_{d=1}^{\min(n,m)}\frac{d}{\varphi(d)}\sum\limits_{k=1}^{\min(\lfloor\frac{n}{d}\rfloor,\lfloor\frac{m}{d}\rfloor)}\mu(k)\sum\limits_{i=1}^{\lfloor\frac{n}{dk}\rfloor}\varphi(idk)\sum\limits_{j=1}^{\lfloor\frac{m}{dk}\rfloor}\varphi(jdk)
T = d k T=dk 那么 k = T d k=\frac{T}{d}
= T = 1 min ( n , m ) d T d φ ( d ) μ ( T d ) i = 1 n T φ ( i T ) j = 1 m T φ ( j T ) =\sum\limits_{T=1}^{\min(n,m)}\sum\limits_{d|T}\frac{d}{\varphi(d)}\mu\left(\frac{T}{d}\right)\sum\limits_{i=1}^{\lfloor\frac{n}{T}\rfloor}\varphi(iT)\sum\limits_{j=1}^{\lfloor\frac{m}{T}\rfloor}\varphi(jT)
设函数 f ( T ) = d T d φ ( d ) μ ( T d ) f(T)=\sum\limits_{d|T}\frac{d}{\varphi(d)}\mu\left(\frac{T}{d}\right) 显然可以 O ( n log n ) O(n\log n) 预处理
再设函数 g ( x , y ) = i = 1 y φ ( i x ) g(x,y)=\sum\limits_{i=1}^y\varphi(ix) 那么原式写成
T = 1 min ( n , m ) f ( T ) g ( T , n T ) g ( T , m T ) \sum\limits_{T=1}^{\min(n,m)}f(T)g\left(T,\lfloor\frac{n}{T}\rfloor\right)g\left(T,\lfloor\frac{m}{T}\rfloor\right)
可是想了想, g g 似乎没有很好的前缀和预处理方法
这时候看了一眼数据范围:多测 1 0 4 10^4 n , m 1 0 5 n,m\leq10^5
这谁顶得住啊???
于是我去翻了题解。不得不说还是有点毒瘤的。
首先注意到所有需要用到的 g ( x , y ) g(x,y) 都满足 x y max ( n , m ) xy\leq\max(n,m) ,所以可以用 std::vector \text{std::vector} 把它们全部预处理出来存好,借助这个简单的递推公式: g ( x , y ) = g ( x , y 1 ) + φ ( x y ) g(x,y)=g(x,y-1)+\varphi(xy)
我们可以设前缀和 s ( x , y , n ) = T = 1 n f ( T ) g ( T , x ) g ( T , y ) s(x,y,n)=\sum\limits_{T=1}^nf(T)g(T,x)g(T,y) ,显然有递推关系 s ( x , y , n ) = s ( x , y , n 1 ) + f ( n ) g ( n , x ) g ( n , y ) s(x,y,n)=s(x,y,n-1)+f(n)g(n,x)g(n,y)
然后我们装个B设一个B,预处理所有 x , y B x,y\leq B s ( x , y , z ) s(x,y,z) ,同样用 std::vector \text{std::vector} 存。注意在预处理的时候 z n max ( x , y ) z\leq\frac{n}{\max(x,y)} ,因此这个动态数组空间复杂度 O ( B B ( n / B ) ) = O ( n B ) O(B\cdot B\cdot (n/B))=O(nB) 可以接受
那么在回答询问的时候,枚举 T T ,对于 n T , m T B \lfloor\frac{n}{T}\rfloor,\lfloor\frac{m}{T}\rfloor\leq B 的情形直接暴力,对于大于 B B 的部分利用预处理的前缀和 s ( x , y , z ) s(x,y,z) 整除分块计算
单个询问的时间复杂度就是 O ( n / B + n ) O(n/B+\sqrt{n}) ,预处理时间复杂度 O ( n log n + n B ) O(n\log n+nB) ,取 B = n 3 B=\sqrt[3]{n} 可以取得比较好的复杂度。
注意科学取模

#include <cctype>
#include <cstdio>
#include <climits>
#include <algorithm>
#include <vector>

template <typename T> inline void read(T& x) {
    int f = 0, c = getchar(); x = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
    if (f) x = -x;
}
template <typename T, typename... Args>
inline void read(T& x, Args&... args) {
    read(x); read(args...); 
}
template <typename T> void write(T x) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + 48);
}
template <typename T> inline void writeln(T x) { write(x); puts(""); }
template <typename T> inline bool chkmin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkmax(T& x, const T& y) { return x < y ? (x = y, true) : false; }

typedef long long LL;

const LL mod = 998244353;
const int maxn = 1e5 + 207;
const int maxsz = 40;
const int B = 35;

int pri[maxn], phi[maxn], mu[maxn];
LL inv[maxn];
bool v[maxn];
LL f[maxn];
std::vector<LL> g[maxn];
std::vector<LL> s[maxsz][maxsz];

inline LL qpow(LL x, LL k) {
    LL s = 1;
    for (; k; x = x * x % mod, k >>= 1)
        if (k & 1) s = s * x % mod;
    return s;
}

inline void sieve(int n) {
    phi[1] = mu[1] = 1;
    for (int i = 2; i <= n; ++i) {
        if (!v[i]) { phi[i] = i - 1; mu[i] = -1; pri[++pri[0]] = i; }
        for (int j = 1; j <= pri[0]; ++j) {
            int x = pri[j] * i;
            if (x > n) break;
            v[x] = 1;
            if (!(i % pri[j])) {
                phi[x] = pri[j] * phi[i];
                mu[x] = 0;
                break;
            } else {
                phi[x] = phi[pri[j]] * phi[i];
                mu[x] = -mu[i];
            }
        }
    }
}

inline void init(int n) {
    inv[1] = 1;
    for (int i = 2; i <= n; ++i)
        inv[i] = (mod - mod / i * inv[mod % i]) % mod;
    for (int d = 1; d <= n; ++d)
        for (int T = d; T <= n; T += d)
            f[T] += d * inv[phi[d]] % mod * mu[T / d];
    for (int j = 1; j <= n; ++j) {
        g[j].push_back(0);
        for (int i = 1; i <= n / j; ++i) {
            LL val = (g[j][i - 1] + phi[i * j]) % mod;
            g[j].push_back(val);
        }
    }
    for (int x = 1; x <= B; ++x)
        for (int y = 1; y <= B; ++y) {
            int len = n / std::max(x, y);
            s[x][y].push_back(0);
            for (int i = 1; i <= len; ++i) {
                LL val = (s[x][y][i - 1] + f[i] * g[i][x] % mod * g[i][y] % mod + mod) % mod;
                s[x][y].push_back(val);
            }
        }
}

inline LL solve(int n, int m) {
    if (n > m) std::swap(n, m);
    LL ans = 0;
    for (int i = 1; i <= m / B; ++i)
        ans = (ans + f[i] * g[i][n / i] % mod * g[i][m / i] % mod + mod) % mod;
    for (int l = m / B + 1, r; l <= n; l = r + 1) {
        r = std::min(n / (n / l), m / (m / l));
        ans = (ans + (s[n / l][m / l][r] - s[n / l][m / l][l - 1]) % mod + mod % mod) % mod;
    }
    return (ans % mod + mod) % mod;
}

int main() {
    int T; read(T);
    sieve(1e5); init(1e5);
    while (T--) {
        int n, m; read(n, m);
        writeln(solve(n, m));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39677783/article/details/89281285