质数
定义
只存在
1
1
1和它本身两个约数的数。
质数分布较稀疏,对于一个正整数
n
n
n,不超过
n
n
n的质数大约有
ln
n
n
\frac{\ln n}{n}
nlnn个。
质数的判定
1、试除法
若一个正整数
n
n
n为合数,则一定存在一个
x
x
x,使
x
∣
n
x|n
x∣n且
x
≤
n
x\leq\sqrt{n}
x≤n。
证明:
由质数的定义可知,一定存在一个正整数
y
y
y,使
x
≤
y
x\leq y
x≤y且
x
y
=
n
x y=n
xy=n。
若
x
≤
n
x\leq\sqrt{n}
x≤n且
y
≤
n
y \leq \sqrt{n}
y≤n,则当且仅当
x
=
y
=
n
x=y=\sqrt{n}
x=y=n时,
x
y
=
n
x y=n
xy=n才成立。故此时
x
=
n
x=\sqrt{n}
x=n。
若
x
>
n
x>\sqrt{n}
x>n且
y
>
n
y>\sqrt{n}
y>n,则
x
y
>
n
x y>n
xy>n。故当
y
>
n
y>\sqrt{n}
y>n时,
x
≤
n
x \leq\sqrt{n}
x≤n。
综上述,对于每个合数
n
n
n,一定存在
x
x
x,使
x
∣
n
x|n
x∣n,且
x
≤
n
x\leq\sqrt{n}
x≤n。
根据此命题,我们只需扫描
[
2
,
n
]
[2,\sqrt{n}]
[2,n]内所有的数,判断其是否是
n
n
n的约数即可。
bool prime(int n) {
for (int i = 2; i <= n; ++i)
if (n % i == 0) return false;
return true;
}
时间复杂度 O ( n ) O(\sqrt{n}) O(n)
2、Eratosthenes筛法
在试除法中,我们通过枚举一个数的约数来判断其是否是质数。而筛法的思想与其相反。对于每个数,我们枚举它的倍数,易证其倍数一定为合数。对于每个合数,我们将其“筛去”,而剩下的数就是质数了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V8v7Hqb4-1685786649350)(https://tse4.mm.bing.net/th?id=OIP.i9a960r19Kg8JXlFbDhmGwAAAA&pid=Api)]
观察上图,可以发现,只需对每个质数
x
x
x,从
x
2
x^2
x2开始枚举倍数,并将其筛去,即可得到质数表。
void sieve(int n, bool *prime) {
memset(prime, true, sizeof is_prime);
prime[1] = false;
for (int i = 2; i <= n; ++i) {
if (!prime[i]) continue;
for (int j = i * i; j <= n; j += i)
prime[j] = false;
}
}
时间复杂度为
O
(
Σ
p
n
p
)
O(\Sigma_{p}\frac{n}{p})
O(Σppn)
=
=
=
O
(
n
log
log
n
)
O(n\log\log n)
O(nloglogn),效率接近线性。
3、线性筛
尽管加了许多优化,Eratosthenes筛法仍然会重复标记合数。可以这样想,如果能够确定每个合数会被哪个质数筛去,就可以在线性时间复杂度内筛除质数表。
于是维护一个
v
v
v数组,
v
i
v_i
vi表示
i
i
i的最小质因数。同时维护一个
p
p
p数组,记录质数表。
void sieve(int n, int &pr, int *p, int *v) {
for (int i = 2; i <= n; ++i) {
if (v[i] == 0) v[i] = i, p[++pr] = i;
// 没被筛,是质数
for (int j = 1; j <= pr; ++j) {
int k = i * p[j];
v[k] = p[j];
if (i % p[j] == 0 || k > n) break;
// 有比p[j]更小的质因子或超出范围
}
}
}
质因数分解
算数基本定理(唯一分解定理)
任何一个大于1的自然数
N
N
N ,都可以唯一分解成有限个质数的乘积。
即
N
=
Π
i
=
1
m
p
i
c
i
N = \Pi_{i=1}^m { p_i ^ { c_i } }
N=Πi=1mpici,其中
p
i
p_i
pi为质数且
p
i
−
1
<
p
i
p_{i-1} < p_i
pi−1<pi,
c
i
c_i
ci为正整数。
这样的分解叫做
N
N
N的标准分解式。
质因数分解算法
1、试除法
对于一个正整数
N
N
N,可以证明至多存在一个大于
N
\sqrt N
N的质因数。所以只需扫描
[
2
,
N
]
[2, \sqrt N]
[2,N]之间的数。对于每个被扫描的数
d
d
d,若
d
d
d为质数且
d
∣
n
d|n
d∣n,则将
d
d
d除尽,并统计其个数。
void decom(int n, int &m, int *p, int *c) {
// 分解 n,分解式储存在 p[1..m] 及 c[1..m] 中
m = 0;
for (int i = 2, size = sqrt(n); i <= size; ++i)
if (n % i == 0) {
p[++m] = i, c[m] = 0;
for (; n % i == 0; n /= i)
++c[m];
}
if (n > 1) p[++m] = n, c[m] = 1; // 特判 n 为质数的情况
}
时间复杂度为
O
(
N
)
O(\sqrt N)
O(N)。
2、短除法
当已经筛出每个数的最小质因数时,可以更加高效的分解质因数。
对于每个正整数
N
N
N,易证其最小质因数
v
(
N
)
v(N)
v(N)一定在其唯一分解式中。则将
N
N
N除以
v
(
N
)
v(N)
v(N)后,可迭代处理
N
v
(
N
)
\frac{N} {v(N)}
v(N)N。边界条件为
N
=
1
N = 1
N=1。
void decom(int n, int &m, int *p, int *c) {
m = 0;
for (; n != 1; n /= v[n])
if (p[m] == v[n]) // 重复,累计个数
++c[m];
else
p[++m] = v[n], c[m] = 1;
}
约数
定义
略。。。
算数基本定理的推论
对于一个正整数
N
N
N:
N
N
N的约数个数为
Π
i
=
1
m
(
c
i
+
1
)
\Pi_{i=1}^m ({c_i + 1})
Πi=1m(ci+1)。
N
N
N的约数和为
Π
i
=
1
m
(
Σ
j
=
0
c
i
p
i
j
)
\Pi_{i=1}^m(\Sigma_{j=0}^{c_i}p_i^j)
Πi=1m(Σj=0cipij)
正约数集合
1、试除法
若
d
∣
N
d|N
d∣N且
d
≤
N
d\leq\sqrt N
d≤N,则定有
N
d
∣
N
\frac{N}{d}|N
dN∣N。即约数总是成对出现的。
因此,只需扫描
[
1
,
N
]
[1,\sqrt N]
[1,N]中的数。对于每个数
d
d
d,判断其能否整除
N
N
N,若能整除,则将
d
d
d和
N
d
\frac{N}{d}
dN同时加入
N
N
N的约数集合中。当
d
=
N
d=\sqrt N
d=N时特判即可。
void calc(int n, int &m, int *d) {
m = 0;
for (int i = 1, size = sqrt(n); i <= size; ++i)
if (n % i == 0) {
d[++m] = i;
if (i != size) d[++m] = n / i;
}
}
时间复杂度为
O
(
N
)
O(\sqrt N)
O(N)。
推论:一个正整数
N
N
N的约数个数上界为
N
\sqrt N
N。
2、迭代法
求
[
1
,
N
]
[1,N]
[1,N]中每个数的约数集合,可以使用迭代法。
扫描每个数
d
d
d,并将其加入其所有范围内的倍数的约数集合中。
void calc(int n, vector<int> *d) {
for (int i = 1; i <= n; ++i)
for (int j = i; j <= n; j += i)
d[j].push_back(i);
}
时间复杂度为
O
(
Σ
i
=
1
N
N
i
)
=
O
(
N
log
N
)
O(\Sigma_{i=1}^N \frac{N}{i})=O(N\log N)
O(Σi=1NiN)=O(NlogN)。
注:调和级数
Σ
i
=
1
N
1
i
=
ln
(
n
+
1
)
+
λ
\Sigma_{i=1}^N \frac{1}{i}=\ln(n+1)+\lambda
Σi=1Ni1=ln(n+1)+λ,其中
λ
\lambda
λ为欧拉常数。
推论:
[
1
,
N
]
[1,N]
[1,N]中每个数的约数个数总和大约为
N
log
N
N\log N
NlogN。
约数和 & 约数个数和
除法分块
详见P2261 [CQOI2007]余数求和。
这是Capella的题解,比较详细。
数论函数
定义
在数论上,数论函数指定义域为正整数、陪域为复数的函数。
积性函数
常见的积性函数
- φ ( n ) \varphi(n) φ(n):欧拉函数
- μ ( n ) \mu(n) μ(n):莫比乌斯函数
- σ k ( n ) \sigma_k(n) σk(n): Σ d ∣ n d k \Sigma_{d|n}d^k Σd∣ndk
积性函数的性质
若 f f f为积性函数,则 ( a , b ) = 1 ⇒ f ( a b ) = f ( a ) f ( b ) (a,b)=1 \Rightarrow f(ab)=f(a)f(b) (a,b)=1⇒f(ab)=f(a)f(b)
线性筛求积性函数
欧拉函数
李煜东的《算法竞赛进阶指南》中讲解很详细,这里补充2条性质:
1、除了
N
=
2
N=2
N=2,
2
∣
φ
(
N
)
2|\varphi(N)
2∣φ(N);
2、当
N
N
N为奇数时,
φ
(
2
N
)
=
φ
(
N
)
\varphi(2N) = \varphi(N)
φ(2N)=φ(N)。
例题
P1390 公约数的和
P2158 [SDOI2008]仪仗队
莫比乌斯函数
略。。。
同余
费马小定理
若 p p p为素数,则对于任意整数 a a a,有 a p ≡ a ( m o d p ) a^p \equiv a (\mod p) ap≡a(modp)
剩余系
欧拉定理
同余方程
乘法逆元
定义
在模 p p p意义下, a × a − 1 ≡ 1 ( m o d p ) a \times a^{-1} \equiv 1 (\mod p) a×a−1≡1(modp), a − 1 a^{-1} a−1成为 a a a的乘法逆元。
利用扩展欧几里得求解
即求解同余方程 a × x ≡ 1 ( m o d p ) a \times x \equiv 1 (\mod p) a×x≡1(modp)
long long inv(long long a) {
long long x, y;
long long d = exgcd(a, p, x, y);
if (d != 1) return -1; // 不存在逆元
return (x % p + p) % p;
}
利用费马小定理求解
若
p
p
p为素数,则
a
p
−
1
≡
1
(
m
o
d
p
)
⇒
a
p
−
2
≡
a
−
1
(
m
o
d
p
)
a^{p-1} \equiv 1 (\mod p) \Rightarrow a^{p-2} \equiv a^{-1}(\mod p)
ap−1≡1(modp)⇒ap−2≡a−1(modp)
即
a
p
−
2
a^{p-2}
ap−2为
a
a
a的乘法逆元。
利用线性筛求解
由逆元的定义可知逆元为完全积性函数,故可以用线性筛求解。
对于素数的逆元,直接用费马小或扩欧,因为
n
n
n以内素数个数为
n
ln
n
\frac{n}{\ln n}
lnnn,所以时间复杂度还是
O
(
n
)
O(n)
O(n)。
void sieve(int n) {
v[1] = 1;
for (int i = 2; i <= n; ++i) {
if (v[i] == 0) {
v[i] = i;
p[++pr] = i;
inv[i] = pow_mod(i, P - 2);
}
for (int j = 1, k; k = i * p[j], j <= pr && k <= n; ++j) {
v[k] = p[j];
inv[k] = inv[i] * inv[p[j]] % P;
if (i % p[j] == 0) break;
}
}
}
线性递推
首先
1
−
1
≡
1
(
m
o
d
p
)
1^{-1} \equiv 1(\mod p)
1−1≡1(modp)
对于
i
i
i,设
k
=
⌊
p
i
⌋
k=\lfloor \frac{p}{i}\rfloor
k=⌊ip⌋,
r
r
r满足
p
=
k
×
i
+
r
p=k\times i + r
p=k×i+r且
0
<
r
<
i
0<r<i
0<r<i
易证
k
×
i
+
r
≡
0
(
m
o
d
p
)
k\times i + r \equiv 0 (\mod p)
k×i+r≡0(modp)
两边同除以
i
,
r
i,r
i,r,得
k
×
r
−
1
+
i
−
1
≡
0
(
m
o
d
p
)
k\times r^{-1} +i^{-1}\equiv 0 (\mod p)
k×r−1+i−1≡0(modp)
移项,得
i
−
1
≡
k
×
r
−
1
(
m
o
d
p
)
i^{-1}\equiv k \times r^{-1} (\mod p)
i−1≡k×r−1(modp)
inv[1] = 1;
for(int i = 2; i <= n; i++)
inv[i] = (p - p / i) * inv[p % i] % p;
反演
略。。。
卷积
略。。。