1. 程式人生 > 其它 >積性函式學習筆記

積性函式學習筆記

積性函式是什麼?

  • 如果當 \(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)\)
    ,那麼 \(f(n)\) 也是積性函式。
    對於質數 \(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\)