数学基础-素数
素数
定义
一个大于 \(1\) 的自然数,除了 \(1\) 和它自身外,不能整除其他自然数的数。
性质
- 素数有无穷多个
- 存在任意长的连续数,其中所有数都是合数,即相邻素数之间的间隔任意大。
- 随着 \(n\) 的增大素数越来越稀疏。
哥德巴赫猜想
任意大于 \(2\) 的正偶数都可以写成两个素数的和,一个充分大偶数必定可以写成一个素数加上一个最多由两个质因子所组成的合数,简称为 \(1+2\)。
贝特朗假设
对任意 \(n\ge1\),\([n, 2n)\) 之间至少存在一个素数。
推论
\(2^n\) 之内至少存在 \(n\) 个素数。
素数定理
设 \(n\ge2\),记 \(\pi(x)\) 表示不超过 \(x\) 的素数的个数,当 \(x\rightarrow\infty\) 时,有:
推论
推论:从不大于 \(n\) 的自然数中随机选一个,它是素数的概率大约是 \(\frac{1}{\ln n}\)。
算术基本定理(唯一分解定理)
任何一个大于 \(1\) 的正整数 \(N\) 都能唯一分解为有限个素数的乘积,可写作:
其中 \(c_i\) 是正整数,\(p_i\) 是素数,且满足 \(p_1<p_2<...<p_m\)。
推论
\(N\) 的正约数的集合可写作:
其中 \(b_i\) 是正整数,且满足 \(0\le b_i\le c_i\)。
\(N\) 的正约数个数为:
\(N\) 的正约数的和为:
质因数分解
引理
\(n\) 中最多含有一个大于 \(\sqrt{n}\) 的因子。
反证法:若有两个大于 \(\sqrt{n}\) 的因子,则相乘会大于 \(n\)。
试除法
用 \(n\) 除以 \([2, \sqrt{n}]\) 之间的质数,遇到质因子就除净,同时记录质因子及其个数,若 \(n\) 最后大于 \(1\),则剩余的数为 \(n\) 大于 \(\sqrt n\) 的唯一质数。时间复杂度为 \(O(\sqrt{n})\)。
例题:
素数判断
void solve() {
int x, t;
cin >> x;
t = x;
vector<int> v;
for (int p : primes) { // 试除法
if (x == 1)
break;
if (x % p == 0) {
v.push_back(p);
while (x % p == 0) {
x /= p;
}
}
}
if (x != 1) {
v.push_back(x);
}
if (v[0] == t) {
cout << "isprime\n";
} else {
cout << "noprime\n";
}
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " \n"[i == v.size() - 1];
}
}
勒让德定理
\(N!\) 中质因子 \(p\) 的个数为:
例题:
阶乘分解
void solve() {
int n;
cin >> n;
sieve(n);
for (int p : primes) {
int cnt = 0;
for (int i = p; i <= n; i *= p) {
cnt += n / i; // 勒让德定理
}
cout << p << " " << cnt << "\n";
}
}
筛法
埃拉托斯特尼筛法
筛掉素数的倍数,同一个数会被筛多遍,时间复杂度为 \(O(n\log\log n)\)。
核心代码:
vector<int> primes, v;
void sieve(int n) {
primes.clear();
v.assign(n + 1, 0);
for (int i = 2; i <= n; i++) {
if (v[i])
continue; // 如果被筛过
primes.push_back(i); // 剩下的为素数
for (int j = i; j * i <= n; j++) { // 筛掉其倍数
v[i * j] = 1;
}
}
}
欧拉筛/线性筛
确保每个合数只会被其最小质因子筛过,时间复杂度为 \(O(n)\)。
核心代码:
void sieve(int n) {
minp.assign(n + 1, 0);
primes.clear();
for (int i = 2; i <= n; i++) {
if (minp[i] == 0) {
minp[i] = i;
primes.push_back(i);
}
for (int p : primes) {
if (i * p > n || p > minp[i])
break;
minp[i * p] = p; // 只被最小质因子筛过
}
}
}
例题:
素数判断
Forsaken喜欢数论
质数距离
欧拉函数
定义
\([1, N)\) 中与 \(N\) 互质的数的个数称为欧拉函数,记作 \(\varphi(N)\)。
性质
若 \(p\) 是质数,则 \(\varphi(p)=p-1, \varphi(p)=(p-1)p^{k-1}\)。
证明:显然,\(\varphi(p)=p-1\)。\([1,p^k]\) 范围内有 \(p^k\) 个数:\(1,2,...,p,...,2p,...,p^k\),其中共有 \(\frac{p^k}{p}\) 个数:\(p,2p,...,p^k\),与 \(p^k\) 不互质,故 \(\varphi(p) = p^k-p^{k-1}=(p-1)p^k\)。
计算公式
由算数基本定理,有 \(N=p_1^{c_1}p_2^{c_2}...p_m^{c_m}\),则
公式求欧拉函数
只需要求一个数的欧拉函数时,先筛出 \([2,\sqrt{n}]\) 之间的质数,再利用试除法求欧拉函数。
void solve() {
sieve(1e5);
int n;
cin >> n;
int phi = n;
for (int p : primes) {
if (n == 1)
break;
if (n % p == 0) {
phi = phi / p * (p - 1);
while (n % p == 0) {
n /= p;
}
}
}
if (n > 1) {
phi = phi / n * (n - 1);
}
cout << phi << "\n";
}
例题:
欧拉函数
筛法求欧拉函数
当需要求多个数的欧拉函数,可利用线性筛法求出欧拉函数。
若 \(i\) 是质数,\(\varphi(i) = i-1\),线性筛中每个合数 \(m\) 都是被最小质因子筛掉的,设 \(p_j\) 是 \(m\) 的最小质因子,则 \(m\) 通过 \(m=p_j \times i\) 筛掉。
(1) 若 \(i\) 能被 \(p_j\) 整除,则 \(i\) 包含了 \(m\) 中的所有质因子。
(1) 若 \(i\) 不能被 \(p_j\) 整除,则 \(i\) 和 \(p_j\) 互质。
例题:
筛法求欧拉函数
vector<int> minp, primes, phi;
int ans = 1;
void sieve(int n) {
primes.clear();
minp.assign(n + 1, 0);
phi.assign(n + 1, 0);
for (int i = 2; i <= n; i++) {
if (minp[i] == 0) {
minp[i] = i;
primes.push_back(i);
phi[i] = i - 1;
}
ans += phi[i];
for (int p : primes) {
if (i * p > n || p > minp[i])
break;
minp[i * p] = p;
if (i % p) {
phi[i * p] = phi[i] * phi[p];
} else {
phi[i * p] = phi[i] * p;
}
}
}
}
积性函数
定义
若当 \(a,b\) 互素时,有 \(f(a\times b)=f(a)\times f(b)\),则称函数 \(f\) 为积性函数。
性质
若 \(f\) 为积性函数,且由算数基本定理,有 \(N=p_1^{c_1}p_2^{c_2}...p_m^{c_m}\),则: