洛谷 P2257 YY的GCD(莫比烏斯反演)

Happig丶發表於2020-09-24

傳送門


題目大意

∑ i = 1 n ∑ i = 1 m [ g c d ( i , j ) = p ] , p ∈ p r i m e s \sum_{i=1}^{n} \sum_{i=1}^m[gcd(i,j)=p],p \in primes i=1ni=1m[gcd(i,j)=p],pprimes

解題思路

首先看到這個 [ g c d ( i , j ) = p ] [gcd(i,j)=p] [gcd(i,j)=p],肯定要聯想到如下變換 g c d ( i , j ) = 1 ⇔ [ 1 n ] ⇔ ∑ d ∣ n μ ( d ) gcd(i,j)=1 \Leftrightarrow [\frac{1}{n}] \Leftrightarrow \sum_{d|n}\mu(d) gcd(i,j)=1[n1]dnμ(d)

因為需要求的是範圍內每種質數的 ∑ i = 1 n ∑ i = 1 m [ g c d ( i , j ) = p ] \sum_{i=1}^{n} \sum_{i=1}^m[gcd(i,j)=p] i=1ni=1m[gcd(i,j)=p]之和,最後的問題就轉化為了:

∑ p ∈ p r i m e s ∑ i = 1 n ∑ i = 1 m [ g c d ( i , j ) = p ] \sum_{p\in primes} \sum_{i=1}^{n} \sum_{i=1}^m[gcd(i,j)=p] pprimesi=1ni=1m[gcd(i,j)=p]

考慮提取出 p p p然後化為上述能等價代換的形式,即:

∑ p ∈ p r i m e s ∑ i = 1 ⌊ n p ⌋ ∑ i = 1 ⌊ m p ⌋ [ g c d ( i , j ) = 1 ] \sum_{p\in primes} \sum_{i=1}^{\lfloor \frac{n}{p} \rfloor} \sum_{i=1}^{\lfloor \frac{m}{p} \rfloor}[gcd(i,j)=1] pprimesi=1pni=1pm[gcd(i,j)=1]

然後再次轉換: ∑ p ∈ p r i m e s ∑ i = 1 ⌊ n p ⌋ ∑ i = 1 ⌊ m p ⌋ ∑ d ∣ g c d ( i , j ) μ ( d ) \sum_{p\in primes} \sum_{i=1}^{\lfloor \frac{n}{p} \rfloor} \sum_{i=1}^{\lfloor \frac{m}{p} \rfloor}\sum_{d|gcd(i,j)}\mu(d) pprimesi=1pni=1pmdgcd(i,j)μ(d)

將最右端的求和提到最前面,改為列舉 d d d

     ∑ p ∈ p r i m e s ∑ d = 1 m i n ( ⌊ n p ⌋ ⌊ m p ⌋ ) μ ( d ) ⌊ n d p ⌋ ⌊ m d p ⌋ ~~~~\sum_{p\in primes} \sum_{d=1}^{min(\lfloor \frac{n}{p} \rfloor\lfloor \frac{m}{p} \rfloor)}\mu(d)\lfloor \frac{n}{dp} \rfloor\lfloor \frac{m}{dp} \rfloor     pprimesd=1min(pnpm)μ(d)dpndpm

這時似乎已經可以寫了,冷靜分析一波時間複雜度, 1 e 7 1e7 1e7內大約有 3 e 6 3e6 3e6個素數,再加上後面的 n \sqrt{n} n ,我們發現應該會 T L E TLE TLE

這時一個至關重要的變換技巧需要知道:

T = d p T=dp T=dp,那麼:

∑ p ∈ p r i m e s ∑ d = 1 m i n ( ⌊ n p ⌋ ⌊ m p ⌋ ) μ ( d ) ⌊ n T ⌋ ⌊ m T ⌋ \sum_{p\in primes} \sum_{d=1}^{min(\lfloor \frac{n}{p} \rfloor\lfloor \frac{m}{p} \rfloor)}\mu(d)\lfloor \frac{n}{T} \rfloor\lfloor \frac{m}{T} \rfloor pprimesd=1min(pnpm)μ(d)TnTm

然後我們改為列舉 T T T,不難發現 T T T會取遍 [ 1 , m i n ( n , m ) ] [1,min(n,m)] [1,min(n,m)],而 d ∣ T d|T dT是已知的,那麼最終得到了如下式子:

∑ T = 1 m i n ( n , m ) ∑ p ∣ T , p ∈ p r i m e s μ ( T p ) ⌊ n T ⌋ ⌊ m T ⌋ \sum_{T=1}^{min(n,m)} \sum_{p|T,p \in primes}\mu(\frac{T}{p})\lfloor \frac{n}{T} \rfloor\lfloor \frac{m}{T} \rfloor T=1min(n,m)pT,pprimesμ(pT)TnTm

∑ p ∣ T , p ∈ p r i m e s μ ( T p ) \sum_{p|T,p \in primes}\mu(\frac{T}{p}) pT,pprimesμ(pT)的含義是對於某個 T T T,其最終的貢獻為所有單個質因子的另外相乘部分的貢獻累積,那麼考慮埃氏篩我們可以預處理每個 T T T的貢獻,最後不要忘了我們要對上述式子整除分塊,需要再對 T T T進行字首和預處理

總體時間複雜度 O ( n l o g n + T n ) O(nlogn+T\sqrt{n}) O(nlogn+Tn )

//
// Created by Happig on 2020/9/24
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>

using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 1e7 + 10;

vector<int> prime;
bitset<maxn> vis;
int mu[maxn];
ll sum[maxn];

void init() {
    vis.reset(), prime.clear();
    mu[1] = 1;
    for (int i = 2; i < maxn; i++) {
        if (!vis[i]) {
            prime.push_back(i);
            mu[i] = -1;
        }
        for (int j = 0; j < prime.size() && i * prime[j] < maxn; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j]) {
                mu[i * prime[j]] = -mu[i];
            } else {
                mu[i * prime[j]] = 0;
                break;
            }
        }
    }
    for (int i = 0; i < prime.size(); i++) {
        int p = prime[i];
        for (int j = p; j < maxn; j += p) {
            sum[j] += mu[j / p];
        }
    }
    for (int i = 1; i < maxn; i++) sum[i] += sum[i - 1];
}

int main() {
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t, n, m;
    init();
    cin >> t;
    while (t--) {
        cin >> n >> m;
        ll ans = 0;
        int up = min(n, m);
        for (int l = 1, r; l <= up; l = r + 1) {
            r = min(n / (n / l), m / (m / l));
            if (r > up) r = up;
            ans += (sum[r] - sum[l - 1]) * (n / l) * (m / l);
        }
        cout << ans << ENDL;
    }
    return 0;
}