積性函式學習筆記
積性函式是什麼?
- 如果當 \(a,b\) 互質時,有 \(f(ab) = f(a) * f(b)\),那麼稱函式 \(f\) 為積性函式。
- 如果當 \(a,b\) 不互質的時候,也有這個性質, 那麼我們稱函式 \(f\) 為完全積性函式。
常用的積性函式
- \(\varphi(n)\):尤拉函式,\(1\) ~ \(n\) 中與 \(n\) 的 \(gcd = 1\) 的數的個數, 即 \(\sum_{i = 1} ^ {n} [gcd(i, n) == 1]\)。
- \(\mu(n)\):莫比烏斯函式,\(\mu(n) = \left\{ \begin{array}{lcl}
1, &n = 1 \\
(-1)^k, &n = p_{1} ^ {c_1} * p_{2} ^ {c_2} * … * p_{k} ^ {c_k} \\
0, &else \\
\end{array} \right\}\)
- \(id(n)\): 恆等函式, \(id(n) = n\)。
- \(\epsilon(n)\):單位函式,\(\epsilon(n) = [n == 1]\)
- \(\sigma_k(n)\):除數函式,\(\sigma_k(n) = \sum_{d|n} d ^ k\),其中 \(\sigma_0(n)\) 表示 \(n\) 的約數個數,常記為 \(d(n)\),\(\sigma_1(n)\) 表示 \(n\) 的約數和。
- \(I(n)\):元函式,\(I(n) = 1\)
尤拉函式的應用
尤拉函式的性質:
- 設 \(p\) 為質數,若 \(p | n\) 且 \(p ^ 2 | n\),則 \(\varphi(n) = \varphi(n / p) * p\)
- 設 \(p\) 為質數,若 \(p | n\) 但 \(p ^ 2 \nmid n\)$,則 \(\varphi(n) = \varphi(n / p) * (p - 1)\)。
- \(\sum_{d|n}\varphi(d) = n\)(\(\varphi(n) * I = id\))。
設 \(f(n) = \sum_{d|n}\varphi(d)\)。
利用 \(\varphi\) 是積性函式的性質,當 \(n, m\) 互質時, \(f(nm) = \sum_{d|nm}\varphi(d) = (\sum_{d|n}\varphi(d)) * (\sum_{d|m}\varphi(d)) = f(n) * (m)\)
對於質數 \(p\),有 \(f(p^k) = \sum_{d|p^k}\varphi(d)\),那麼能整除 \(p ^ k\) 的數就是 \(1, p, p ^ 2 , … ,p ^ k\)。
那麼 \(f(p ^ k) = \varphi(1) + \varphi(p) + … + \varphi(p ^ k)\)。
根據上述的尤拉函式的第一個性質,可以得到,除了 \(1\) 以外的數,構成了一個等比數列,\((p - 1) * p ^ i\),那麼對等比數列求和後,再加上前面的一個 \(1\),就得到了,\(f(p ^ k) = p ^ k\)。
對每一個數質因數分解後,利用積性函式的性質,就得到了 \(f(n) = f(p_{1} ^ {c_1}) * … * f(p_{m} ^ {c_m}) = p_{1} ^ {c_1} * … * p_{m} ^ {c_m} = n\)。
GCD SUM
P2398 GCD SUM
題目大意:
求 \(\sum_{i = 1} ^ {n} \sum_{j = 1} ^ {n} gcd(i,j)\)
反演入門題。
利用上述尤拉函式的性質 \(3\),可以得到 \(gcd(i,j) = \sum_{d \mid gcd(i,j)}\varphi(d)\)。
所以上柿就被反演為 \(\sum_{i = 1} ^ {n} \sum_{j = 1} ^ {n}\sum_{d \mid gcd(i,j)}\varphi(d)\)。
將內層的 \(d\) 拿到外層來列舉,得到 \(ans = \sum_{d = 1} ^ {n} \sum_{i = 1} ^ {\lfloor \frac{n}{d} \rfloor} \sum_{j = 1} ^ {\lfloor \frac{n}{d} \rfloor} 1\)。
顯然後面的等於 \(\lfloor \frac{n}{d} \rfloor * \lfloor \frac{n}{d} \rfloor\)。
所以 \(ans = \sum_{d = 1} ^ {n} \varphi(d) * \lfloor \frac{n}{d} \rfloor * \lfloor \frac{n}{d} \rfloor\)。
化成這樣的柿子之後,我們就能運用整除分塊求解問題。
整除分塊
可以發現,對於 \(1\leq i \leq n\),顯然有一段 \(i\),它們的 \(\lfloor \frac{i}{d} \rfloor\) 值相等。
所以我們可以把前面的 \(\varphi\) 看成是係數,合併同類項。
然後就能一段一段的求出答案。
因為 \(\lfloor \frac{n}{d} \rfloor\) 的值有 \(\sqrt{n}\) 種,所以這樣的塊就有 \(\sqrt n\) 個,所以我們一共要計算 \(\sqrt n\) 次,時間複雜度就是 \(O(\sqrt n)\)。
預處理
根據上述尤拉函式的性質 \(1, 2\)。
我們可以線上性篩的過程中將 \(\varphi\) 的值遞推出來。
具體實現請參見程式碼。
GCD SUM 程式碼
#include<cstdio>
#include<cstring>
#define LL long long
const int N = 1e5 + 5;
int Prime[N + 5], m, n; bool notPrime[N + 5];
LL phi[N + 5], ans = 0;
void euler() {
memset(notPrime, false, sizeof(notPrime));
m = 0; phi[1] = 1;
for(int i = 2; i <= N; i++) {
if(!notPrime[i]) {
Prime[++m] = i;
phi[i] = i - 1;
}
for(int j = 1; j <= m && 1ll * i * Prime[j] <= N; j++) {
notPrime[i * Prime[j]] = true;
if(i % Prime[j] == 0) {
phi[i * Prime[j]] = phi[i] * Prime[j];
break;
}
phi[i * Prime[j]] = phi[i] * phi[Prime[j]];
}
}
for(int i = 1; i <= N; i++)
phi[i] += phi[i - 1];
}
int main() {
euler();
scanf("%d", &n);
for(int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
ans += (phi[r] - phi[l - 1]) * (n / l) * (n / l);
}
printf("%lld\n", ans);
return 0;
}
## 推薦練習
[簡單的數學題](https://www.luogu.com.cn/problem/P3768)
[公約數的和](https://www.luogu.com.cn/problem/P1390)
莫比烏斯函式的應用
莫比烏斯函式的性質:
- \(\sum_{d | n}\mu(d) = \epsilon(n)\)。
根據 \(\mu\) 是容斥係數的性質即可證明。
ZAP-Queries
ZAP-Queries
題目大意:求 \(\sum_{i = 1} ^ {a} \sum_{j = 1} ^ {b} [gcd(i,j) == d]\)。
這個柿子等價於 \(\sum_{i = 1} ^ {\lfloor \frac{a}{d} \rfloor} \sum_{j = 1} ^ {\lfloor \frac{b}{d} \rfloor} [gcd(i,j) == 1]\)。
那麼 \([gcd(i,j) == 1]\) 就可以反演為 \(\sum_{d | n}\mu(d)\),由上述性質可得,只有當 \(gcd(i,j) == 1\) 時,\(\sum_{d | n}\mu(d)\) 才為 \(1\),符合條件。
那麼原柿子就可化為 \(\sum_{i = 1} ^ {\lfloor \frac{a}{d} \rfloor} \sum_{j = 1} ^ {\lfloor \frac{b}{d} \rfloor} \sum_{k | n}\mu(k)\)。
按照上文的套路,就有 \(ans = \sum_{k = 1} ^ {n} \mu(k) * \lfloor \frac{a}{dk} \rfloor * \lfloor \frac{b}{dk} \rfloor\)。
那麼我們就能利用整除分塊求解了。
兩個上界的整除分塊
\(r\) 的取值方法有所變化。
\(r = min(a / (a / l), b / (b / l))\)。
因為我們要同時滿足 \(a,b\)。
預處理
篩到質數令 \(mu = -1\),篩出的數能被 \(p ^ 2\) 整除的數令 \(mu = 0\),不能被整除的數令 \(mu[i * p] = -mu[i]\)。
詳情請參見程式碼。
程式碼
和下面的有所的同,下面那個是以前學的時候寫的,現在喜歡這樣寫:
void euler() {
memset(notPrime, false, sizeof(notPrime));
m = 0; mu[1] = 1;
for(int i = 2; i <= N; i++) {
if(!notPrime[i]) {
Prime[++m] = i;
mu[i] = -1;
}
for(int j = 1; j <= m && 1ll * i * Prime[j] <= N; j++) {
notPrime[i * Prime[j]] = true;
if(i % Prime[j] == 0) {
mu[i * Prime[j]] = 0;
break;
}
mu[i * Prime[j]] = -mu[i];
}
}
for(int i = 1; i <= N; i++)
mu[i] += mu[i - 1];
}
ZAP-Queries 程式碼
#include<cstdio>
#include<algorithm>
const int N = 1e5 + 5;
int prime[N],mu[N],pre[N],t,num = 0;
bool notprime[N];
void init()
{
mu[1] = 1;
for(int i = 2;i <= N;i++)
{
if(!notprime[i]) prime[++num] = i,mu[i] = -1;
for(int j = 1;j <= num && i * prime[j] <= N;j++)
{
notprime[i * prime[j]] = true;
if(i % prime[j] == 0){mu[i * prime[j]] = 0;break;}
mu[i * prime[j]] = mu[i] * mu[prime[j]];
}
}
for(int i = 1;i <= N;i++)
pre[i] = pre[i - 1] + mu[i];
}
int solve(int a,int b)
{
int ans = 0;
for(int i = 1,j;i <= a;i = j + 1)
{
j = std::min(a / (a / i),b / (b / i));
ans += (pre[j] - pre[i - 1]) * (a / i) * (b / i);
}
return ans;
}
inline int read()
{
int x = 0,flag = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') flag = -1;ch = getchar();}
while(ch >='0' && ch <='9'){x = (x << 3) + (x << 1) + ch - 48;ch = getchar();}
return x * flag;
}
int main()
{
init();
t = read();
for(int i = 1;i <= t;i++)
{
//int a = read(),b = read(),c = read(),d = read(),k = read();
int b = read(),d = read(),k = read();
if(b > d) std::swap(b,d);
printf("%d\n",solve(b / k,d / k));
}
return 0;
}
推薦練習
[國家集訓隊]Crash的數字表格 / JZPTAB
[CQOI2015]選數
數表
需要注意的地方
- 初始化不要忘了 \(\varphi_1 = \mu_1 = 1\)。
- 求字首和要等篩完了再求。
- 注意篩數的陣列不要越界了。
- 資料範圍較大的時候在 \(Prime[j] * i <= N\) 處要強轉 \(long long\)。