「Bzoj 2301」「HAOI2011」Problem b(莫比乌斯反演)

BZOJ 2301
题意:给定$a, b, c, d, k$, 求
$$
\sum_{i=a}^{b} \sum_{j=c}^{d} [\gcd(i,j)=k]
$$

(下面默认$n \leq m$, 不满足则交换)
解:
我们可以将答案分成几个部分求解,然后$ans=f(b,d)-f(a-1,d)-f(b,c-1)+f(a-1,c-1)$
依题意设出$f(k)$为$gcd(i,j)=k$的个数$(1 \leq i \leq n, 1 \leq j \leq n)$
但是这个并不好求,看见两个求和符号,我们可以想到用莫比乌斯反演来求解。
显然用变形后的两个式子,我们就可以设$g(k)$为$k|gcd(i,j)$的个数$(1 \leq i \leq n, 1 \leq j \leq n)$
$g(k)$非常好算,$g(k)=\lfloor \frac n{k} \rfloor \lfloor \frac m{k} \rfloor$
而显然$g(k)=\sum_{d=1}^{\lfloor \frac nk \rfloor} f(ki)$
那么可以莫比乌斯反演
$$
f(k)=\sum_{d=1}^{\lfloor \frac nk \rfloor} \mu(d) \lfloor \frac n{kd} \rfloor \lfloor \frac m{kd} \rfloor
$$
换元$\lfloor \frac nk \rfloor$,整除分块即可。

注意有$n,m$的整除分块是取两者最小值,复杂度也是有保证的。

#include<cstdio> 
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<set>
#include<cmath>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db double
#define fir first
#define sec second
#define mp make_pair
using namespace std;

namespace flyinthesky {

    const LL MAXN = 50000 + 5;

    LL q, tot, pri[MAXN], vis[MAXN], mu[MAXN];

    LL getans(LL n, LL m, LL k) {
        if (n > m) swap(n, m);
        n /= k, m /= k;
        LL l = 1, r, ans = 0ll;
        while (l <= n) {
            r = min((n / (n / l)), (m / (m / l)));
            ans += (n / l) * (m / l) * (mu[r] - mu[l - 1]);
            l = r + 1ll;
        }
        return ans;
    }

    void clean() {
        ms(vis, 0), tot = 0;
    }
    int solve() {
        scanf("%lld", &q);
        clean();
        vis[1] = 1, mu[1] = 1;
        for (LL i = 2; i <= 50000; ++i) {
            if (!vis[i]) mu[i] = -1, pri[++tot] = i;
            for (LL j = 1; j <= tot && i * pri[j] <= 50000; ++j) {
                vis[i * pri[j]] = 1;
                if (i % pri[j] == 0) {
                    mu[i * pri[j]] = 0;
                    break;
                } else mu[i * pri[j]] = -mu[i];
            }
        }
        //for (LL i = 1; i <= 100; ++i) cerr << mu[i] << " ";
        mu[0] = 0;
        for (LL i = 1; i <= 50000; ++i) mu[i] += mu[i - 1];
        for (LL a, b, c, d, k, i = 1; i <= q; ++i) {
            scanf("%lld%lld%lld%lld%lld", &a, &b, &c, &d, &k);
            printf("%lld\n", getans(b, d, k) - getans(a - 1, d, k) - getans(b, c - 1, k) + getans(a - 1, c - 1, k));
        }
        return 0; 
    }
}
int main() {
    flyinthesky::solve();
    return 0;
}
------ 本文结束 ------