1. 程式人生 > 其它 >AcWing 220. 最大公約數

AcWing 220. 最大公約數

題目傳送門

一、視訊教程

https://www.bilibili.com/video/BV1cP4y1c7q8

二、解題思路

最開始讀錯題,成了:
\(1<=x,y<=N\),並且\(gcd(x,y)=1\)有多少數對?

這不就是在計算\(\displaystyle \sum_{i=1}^{N}φ(i)\)嗎?

其實本題不是說\(gcd(x,y)=1\),而是說\(gcd(x,y)=p\),其中\(p\)是質數

1、解讀樣例

先來看一下樣例:\(n=4\)時,答案是\(4\)
我們手動列舉一下:
\(gcd(2,2)=2\),\(gcd(3,3)=3\),\(gcd(2,4)=2\),\(gcd(4,2)=2\)

就這四個,因為只有這幾種組合,最大公約數才是質數,組數是\(4\)組。
可以看得出來,由這個樣例推出:這裡\(gcd(2,4)=gcd(4,2)=2\)被看成了兩組,也說是說對於不同的\(x,y\),視為兩組結果。
小結:手動計算一下樣例非常重要,可以幫我們更深刻理解題意!

2、推式子

這是一個最大公約數的典型轉化技巧:把\(p\)除過來,形成:

\[gcd(\frac{x}{p},\frac{y}{p})=1 \]

為什麼要除過來呢?因為\(p\)的取值是多種多樣的,我們不知道\(p\)具體是什麼,如果我們把\(p\)除過來,是不是就會簡化很多呢?其實就是從骨子裡想要用尤拉函式解決問題,不搞成和尤拉函式差不多的樣子,怎麼能用得上人家呢?除吧,技巧,背吧,技巧~

這是在最大公約數中最常見的變化技巧之一,後面還有比較難的莫比烏斯變換,以後再講~

現在,可以視\(x'=\frac{x}{p},y'=\frac{y}{p}\),即就是\(gcd(x',y')=1\),這不就是用上尤拉函數了嗎?只不過資料範圍有了變化,變成了\(1<=x',y'<=\frac{N}{p}\)

到了這裡,\(p\)是啥呢?它是需要在\(1\sim N\)之間列舉的每一個質數,用線性篩法求得,然後遍歷就可以計算了。

   for (int i = 0; i < cnt; i++) {
        int p = primes[i]; //列舉1~n中的所有質數p
        ......
   }

如果\(p\)被列舉到,比如現在\(p=2\),那我們如何來求\(gcd(x',y')=1\)的數對個數呢?
這就是簡單的尤拉函式+求和(可以使用字首和優化)啊~
資料範圍:\(1<=x',y'<=\frac{N}{p}\)

\(201\)一樣,提前計算出字首和,這樣避免每次迴圈計算:

  //維護一個字首和,本題中phi[1]=1
    for (int i = 1; i <= n; i++) s[i] = s[i - 1] + phi[i];

這裡有一個第一象限\(45^。\)直線上點的問題,就是\(x'=y'\)時,我們分別在兩下方和上方計算了兩次,需要再減去\(1\)

  res += s[n / p] * 2 - 1; //推導的公式
\[\sum_{i=1}^N (2 * \sum_{i=1}^{\frac{N}{p}} φ(i)-1 ) \]

三、實現程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e7 + 10; //這傢伙好大啊

int primes[N], cnt;
bool st[N];
int phi[N];
LL s[N];

void euler(int n) {
    phi[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!st[i]) {
            primes[cnt++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; primes[j] * i <= n; j++) {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) {
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            phi[i * primes[j]] = phi[i] * (primes[j] - 1);
        }
    }
}

int main() {
    int n;
    cin >> n;
    euler(n);
    //維護一個字首和,本題中phi[1]=1
    for (int i = 1; i <= n; i++) s[i] = s[i - 1] + phi[i];

    LL res = 0;
    for (int i = 0; i < cnt; i++) {
        int p = primes[i];       //列舉1~n中的所有質數p
        res += s[n / p] * 2 - 1; //推導的公式
    }
    cout << res << endl;
    return 0;
}