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;
}