1. 程式人生 > >luogu2257 YY的GCD(莫比烏斯反演)

luogu2257 YY的GCD(莫比烏斯反演)

luogu2257

題目描述:神犇YY虐完數論後給傻×kAc出了一題
     給定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)為質數的(x, y)有多少對
     kAc這種傻×必然不會了,於是向你來請教……
     多組輸入

輸入格式:第一行一個整數T 表述資料組數
     接下來T行,每行兩個正整數,表示N, M

輸出格式:T行,每行一個整數表示第i組資料的結果

輸入樣例:
2
10 10
100 100

輸出樣例:
30
2791

解析:莫比烏斯反演中比較基礎的題,也是我第一次做的莫比烏斯反演題。
   設f(n)表示gcd為n的數對的對數,F(n)表示gcd為n或n的倍數的數對的對數。
   那麼顯然 \(F(n) = \sum_{n|d}f(d)\)


   反演之後得 \(f(n) = \sum_{n|d}μ(⌊\frac{d}{n}⌋)F(d)\)
   而 F(n) = \(⌊\frac{N}{n}⌋⌊\frac{M}{n}⌋\)
   所以可得 \(f(n) = \sum_{n|d}μ(⌊\frac{d}{n}⌋)⌊\frac{N}{n}⌋⌊\frac{M}{n}⌋\)
   所求的 ans = \(\sum_{n∈prim}f(n)\)
   ans = \(\sum_{n∈prim}\sum_{n|d}μ(⌊\frac{d}{n}⌋)⌊\frac{N}{n}⌋⌊\frac{M}{n}⌋\)
   換一個列舉項,列舉 \(⌊\frac{d}{n}⌋\)
那麼 ans = \(\sum_{n∈prim}\sum_{d=1}^{min(⌊\frac{N}{n}⌋,⌊\frac{M}{n}⌋)}μ(d)⌊\frac{N}{dn}⌋⌊\frac{M}{dn}⌋\)
   將dn換成t,則 ans = \(\sum_{T=1}^{min(N,M)}\sum_{t|T,t∈prim}μ(⌊\frac{T}{t}⌋)⌊\frac{N}{T}⌋⌊\frac{M}{T}⌋\)
   即 ans = \(\sum_{T=1}^{min(N,M)}⌊\frac{N}{T}⌋⌊\frac{M}{T}⌋\sum_{t|T,t∈prim}μ(⌊\frac{T}{t}⌋)\)

   到了這裡,只要套上一個整除分塊就做好了
   μ可以直接篩出來,維護一個字首和即可

程式碼如下:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 1e7 + 5;
int q, mu[maxn], mark[maxn], prim[maxn], tot, g[maxn], n, m;
ll sum[maxn], ans;

int read(void) {
    char c; while (c = getchar(), c < '0' || c >'9'); int x = c - '0';
    while (c = getchar(), c >= '0' && c <= '9') x = x * 10 + c - '0'; return x;
}

void get_mu(void) { //篩mu函式 
    mu[1] = 1; mark[1] = 1;
      for (int i = 2; i <= maxn - 5; ++ i) {
        if (!mark[i]) mu[i] = -1, prim[++ tot] = i;
        for (int j = 1; j <= tot; ++ j) {
            if (prim[j] * i > maxn - 5) break;
            mark[prim[j] * i] = 1;
            if (i % prim[j] == 0) break;
              else mu[i * prim[j]] = -mu[i];
          }
      }
      for (int i = 1; i <= tot; ++ i) //統計mu函式的字首和 
        for (int j = 1; j * prim[i] <= maxn - 5; ++ j) g[j * prim[i]] += mu[j];
      for (int i = 1; i <= maxn - 5; ++ i) sum[i] = sum[i - 1] + g[i];
}

int main() {
    q = read();
    get_mu();
      while (q --) {
        ans = 0; 
        n = read(); m = read();
          for (int l = 1, r; l <= min(n, m); l = r + 1) { //整數分塊統計答案 
              r = min(n / (n / l), m / (m / l));
              ans += 1ll * (n / l) * (m / l) * (sum[r] - sum[l - 1]);
            }
        printf("%lld\n", ans);
      }
    return 0;
}