注意:|:为整除符号。
一、质数
质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
1.试除法判定质数
根据定义可知,我们只需要从
(
i
=
1
)
(i = 1)
(i=1)一直枚举到
(
i
=
n
−
1
)
(i= n - 1)
(i=n−1),在循环内判断n % i == 0
即可。
bool prime(int n)
{
if(n < 2) return false;
for(int i = 2; i < n; i ++ )
{
if(n % i == 0) return false;
}
return true;
}
时间复杂度:
O
(
n
)
O(n)
O(n)
显然这个算法的时间复杂度是非常高的!
OK,下面我们对这个算法进行优化!
如果
d
∣
n
d | n
d∣n,则
n
d
∣
n
\frac{n}{d} |n
dn∣n。即
n
n
n的所有约数都是成对出现的!
ex:
n
=
12
n = 12
n=12,
d
=
3
d = 3
d=3;则3是12的约数,
12
3
\frac{12}{3}
312也是12的约数!
利用这个性质我们只需要枚举
n
n
n最小的那个约数即可:
d
≤
n
d
⇒
d
2
≤
n
⇒
d
≤
n
d\leq\frac{n}{d} \Rightarrow d^2\leq n \Rightarrow d\leq \sqrt{n}
d≤dn⇒d2≤n⇒d≤n
则我们只需要枚举到
n
\sqrt{n}
n即可!
相关题目:AcWing 866. 试除法判定质数
bool prime(int n)
{
if(n < 2) return false;
for(int i = 2; i <= n / i; i ++ )
{
if(n % i == 0) return false;
}
return true;
}
时间复杂度:
O
(
n
)
O(\sqrt n)
O(n)
我们将
O
(
n
)
O(n)
O(n)的时间复杂度降到了
O
(
n
)
O(\sqrt n)
O(n)!
2.分解质因数
可以根据上一步所优化的结果进行枚举。
i
i
i从
2
2
2枚举到
n
\sqrt{n}
n即可。
代码如下就不过多赘述!
相关题目:AcWing 867. 分解质因数
void get_divide(int n)
{
for(int i = 2; i <= n / i; i ++ )
{
if(n % i == 0)
{
int res = 0;
while(n % i == 0)
{
res ++ ;
n /= i;
}
printf("%d %d\n", i, res);
}
}
if(n > 1) printf("%d %d\n", n, 1);
puts("");
}
3.筛质数
相关题目:AcWing 868. 筛质数
3.1埃氏筛法
由于一个质数只能被本身和1整除:
设
n
n
n是以一个质数,
a
,
b
∈
Z
+
a,b\in Z^+
a,b∈Z+,
a
≠
1
,
a
≠
n
a\not=1,a\not=n
a=1,a=n,
b
≠
1
,
b
≠
n
b\not=1,b\not=n
b=1,b=n;
则
n
≠
a
∗
b
n\not=a*b
n=a∗b(即一个质数不能被除1和本身外的任何两个数的乘积所表示),根据这个性质可得,一个质数的倍数一定是合数,我们只需筛掉每个质数的倍数,剩下的就是质数!
时间复杂度:
O
(
n
l
o
g
l
o
g
n
)
O(nloglogn)
O(nloglogn)
//布尔数组st[i] = false代表i这个数为质数否则为合数
void get_prime(int n)
{
for(int i = 2; i <= n; i ++ )
{
if(!st[i])
{
primes[cnt ++ ] = i;
for(int j = i + i; j <= n; j += i) st[j] = true;
}
}
}
3.2线性筛法
其实我们还可以进一步的去降低时间复杂度,因为每一个合数有且仅有一个最小质因子,我们用这个最小质因子去筛,则每一个合数肯定只会被筛一次!
分类讨论:
如果
p
r
i
m
e
(
j
)
prime(j)
prime(j)是
i
i
i的最小质因子,那就筛掉
p
r
i
m
e
(
j
)
∗
i
prime(j) * i
prime(j)∗i,并且跳出循环;
如果
p
r
i
m
e
(
j
)
prime(j)
prime(j)不是
i
i
i的最小质因子,因为枚举质数的时候是从小->大枚举的,所以
p
r
i
m
e
(
j
)
prime(j)
prime(j)一定小于
i
i
i的最小质因子,而
p
r
i
m
e
(
j
)
∗
i
prime(j) * i
prime(j)∗i的最小质因子就一定是
p
r
i
m
e
(
j
)
prime(j)
prime(j),也将
p
r
i
m
e
(
j
)
∗
i
prime(j) * i
prime(j)∗i筛掉,继续枚举到
i
i
i的最小质因子。
时间复杂度: O ( n ) O(n) O(n)
void get_prime(int n)
{
for(int i = 2; i <= n; i ++ )
{
if(!st[i]) primes[cnt ++ ] = i;
for(int j = 0; primes[j] <= n / i; j ++ )
{
st[i * primes[j]] = true;
if(i % primes[j] == 0) break;
}
}
}
二、约数
约数,又称因数。整数 a a a除以整数 b ( b ≠ 0 ) b(b\not=0) b(b=0)除得的商正好是整数而没有余数,我们就说 a a a能被 b b b整除,或 b b b能整除 a a a。 a a a称为 b b b的倍数, b b b称为 a a a的约数。
1.试除法求约数
a
a
a是一个实数,
d
∣
a
d|a
d∣a那么
a
d
∣
a
\frac{a}{d}|a
da∣a,一个数的约数一定是成对存在的!跟上面类似,只需要枚举到
n
\sqrt{n}
n即可。
相关题目:AcWing 869. 试除法求约数
void get_divisor(int n)
{
for(int i = 1; i <= n / i; i ++ )
{
if(n % i == 0)
{
d[cnt ++ ] = i;
//不能重复加相同的元素
if(i != n / i) d[cnt ++ ] = n / i;
}
}
}
时间复杂度: O ( n ) O(\sqrt{n}) O(n)
2.约数的个数
唯一分解定理:每个大于1的自然数,要么本身就是质数,要么可以写为2个或以上的质数的积,而且这些质因子按大小排列之后,写法仅有一种方式。
设
A
∈
N
+
A\in N^+
A∈N+,
p
i
p_{i}
pi为质数,
α
i
∈
N
\alpha_i \in N
αi∈N,则一定有下面这个等式:
A
=
p
1
α
1
∗
p
2
α
2
∗
…
∗
p
n
α
n
A=p_{1}^{\alpha_{1}} * p_{2}^{\alpha_{2}} * \ldots * p_{n}^{\alpha_{n}}
A=p1α1∗p2α2∗…∗pnαn
根据排列组合的知识可知每个
p
i
p_i
pi的幂都可以从
[
0
,
α
i
]
[0, \alpha_i]
[0,αi]中任取,其可选择的次数为
1
+
α
i
1+\alpha_i
1+αi,由此可以推出约数定理:
cnt
(
A
)
=
(
1
+
α
1
)
∗
(
1
+
α
2
)
∗
…
∗
(
1
+
α
n
)
\operatorname{cnt}(A)=\left(1+\alpha_{1}\right) *\left(1+\alpha_{2}\right) * \ldots *\left(1+\alpha_{n}\right)
cnt(A)=(1+α1)∗(1+α2)∗…∗(1+αn)
c
n
t
(
A
)
cnt(A)
cnt(A)为
A
A
A的约数个数。
相关题目链接:acwing 871.约数的个数
#include <iostream>
#include <unordered_map>
typedef long long ULL;
using namespace std;
const int mod = 1e9 + 7;
int main()
{
int n;
scanf("%d", &n);
unordered_map<int, int> primes;
while(n -- )
{
int x;
scanf("%d", &x);
for(int i = 2; i <= x / i; i ++ )
{
while(x % i == 0)
{
primes[i] ++ ;
x /= i;
}
}
if(x > 1) primes[x] ++ ;
}
ULL res = 1;
for(auto prim : primes) res = res * (prim.second + 1) % mod;
printf("%d", res);
return 0;
}
3.约数之和
约数和定理:
Sum
(
A
)
=
(
1
+
p
1
1
+
p
1
2
+
…
+
p
1
α
1
)
∗
(
1
+
p
2
1
+
p
2
2
+
…
+
p
2
α
2
)
∗
…
∗
(
1
+
p
n
1
+
p
n
2
+
…
+
p
n
α
n
)
\operatorname{Sum}(A)=\left(1+p_{1}^{1}+p_{1}^{2}+\ldots+p_{1}^{\alpha_{1}}\right) *\left(1+p_{2}^{1}+p_{2}^{2}+\ldots+p_{2}^{\alpha_{2}}\right) * \ldots *\left(1+p_{n}^{1}+p_{n}^{2}+\ldots+p_{n}^{\alpha_{n}}\right)
Sum(A)=(1+p11+p12+…+p1α1)∗(1+p21+p22+…+p2α2)∗…∗(1+pn1+pn2+…+pnαn)
其中
S
u
m
(
A
)
Sum(A)
Sum(A)为实数
A
A
A的约数之和,
p
i
p_{i}
pi为质数,
α
i
∈
N
\alpha_i \in N
αi∈N。
证明过程如下:
由唯一分解定理可得
A
=
p
1
α
1
∗
p
2
α
2
∗
…
∗
p
n
α
n
A=p_{1}^{\alpha_{1}} * p_{2}^{\alpha_{2}} * \ldots * p_{n}^{\alpha_{n}}
A=p1α1∗p2α2∗…∗pnαn
其中
A
∈
N
+
A\in N^+
A∈N+,
p
i
p_{i}
pi为质数,
α
i
∈
N
\alpha_i \in N
αi∈N。
可知
p
1
α
1
p_{1}^{\alpha_{1}}
p1α1的约数有:
p
1
0
p_{1}^{0}
p10,
p
1
1
p_{1}^{1}
p11,
p
1
2
p_{1}^{2}
p12…
p
1
α
1
p_{1}^{\alpha_{1}}
p1α1
同理可得
p
k
α
k
p_{k}^{\alpha_{k}}
pkαk的约数有:
p
k
0
p_{k}^{0}
pk0,
p
k
1
p_{k}^{1}
pk1,
p
k
2
p_{k}^{2}
pk2…
p
k
α
k
p_{k}^{\alpha_{k}}
pkαk
我们把
p
1
α
1
p_{1}^{\alpha_{1}}
p1α1~
p
n
α
n
p_{n}^{\alpha_{n}}
pnαn的所有约数都加起来,得到约数和定理
Sum
(
A
)
=
(
1
+
p
1
1
+
p
1
2
+
…
+
p
1
α
1
)
∗
(
1
+
p
2
1
+
p
2
2
+
…
+
p
2
α
2
)
∗
…
∗
(
1
+
p
n
1
+
p
n
2
+
…
+
p
n
α
n
)
\operatorname{Sum}(A)=\left(1+p_{1}^{1}+p_{1}^{2}+\ldots+p_{1}^{\alpha_{1}}\right) *\left(1+p_{2}^{1}+p_{2}^{2}+\ldots+p_{2}^{\alpha_{2}}\right) * \ldots *\left(1+p_{n}^{1}+p_{n}^{2}+\ldots+p_{n}^{\alpha_{n}}\right)
Sum(A)=(1+p11+p12+…+p1α1)∗(1+p21+p22+…+p2α2)∗…∗(1+pn1+pn2+…+pnαn)
下面我们介绍代码中所用到的秦九韶算法
设一个
n
n
n次多项式
f
(
x
)
=
a
n
x
n
+
a
n
−
1
x
n
−
1
+
⋯
+
a
1
x
+
a
0
f(x)=a_{n} x^{n}+a_{n-1} x^{n-1}+\cdots+a_{1} x+a_{0}
f(x)=anxn+an−1xn−1+⋯+a1x+a0
我们可以提取公共因子
x
x
x,改写成如下
f
(
x
)
=
(
(
a
n
x
n
−
2
+
a
n
−
1
x
n
−
3
+
⋯
a
3
x
+
a
2
)
x
+
a
1
)
x
+
a
0
\begin{gathered} f(x)=\left(\left(a_{n} x^{n-2}+a_{n-1} x^{n-3}+\cdots a_{3} x+a_{2}\right) x+a_{1}\right) x+a_{0} \end{gathered}
f(x)=((anxn−2+an−1xn−3+⋯a3x+a2)x+a1)x+a0
一直提取
x
x
x,改写成如下
f
(
x
)
=
(
…
(
(
a
n
x
+
a
n
−
1
)
x
+
a
n
−
2
)
x
+
⋯
+
a
1
)
x
+
a
0
\begin{gathered} f(x) =\left(\ldots\left(\left(a_{n} x+a_{n-1}\right) x+a_{n-2}\right) x+\cdots+a_{1}\right) x+a_{0} \end{gathered}
f(x)=(…((anx+an−1)x+an−2)x+⋯+a1)x+a0
求多项式的值时,首先计算最内层括号内一次多项式的值,即
v
2
=
v
1
x
+
a
n
−
2
v
3
=
v
2
x
+
a
n
−
3
⋮
v
n
=
v
n
−
1
x
+
a
0
\begin{gathered} v_{2}=v_{1} x+a_{n-2} \\ v_{3}=v_{2} x+a_{n-3} \\ \vdots \\ v_{n}=v_{n-1} x+a_{0} \end{gathered}
v2=v1x+an−2v3=v2x+an−3⋮vn=vn−1x+a0
结论:对于一个n次多项式,至多做n次乘法和n次加法。
相关题目链接:acwing 871.约数之和
#include <iostream>
#include <unordered_map>
using namespace std;
typedef long long ULL;
const int mod = 1e9 + 7;
int main()
{
int n;
scanf("%d", &n);
unordered_map<int, int> primes;
while(n -- )
{
int x;
scanf("%d", &x);
for(int i = 2; i <= x / i; i ++ )
{
while(x % i == 0)
{
x /= i;
primes[i] ++ ;
}
}
if(x > 1) primes[x] ++ ;
}
ULL res = 1;
for(auto prim : primes)
{
int p = prim.first, k = prim.second;
ULL t = 1;
//秦九韶算法
while(k -- ) t = (t * p + 1) % mod;
res = (res * t) % mod;
}
printf("%d", res);
return 0;
}
4.最大公约数
设
d
∣
a
x
d|ax
d∣ax,
d
∣
b
y
d|by
d∣by(即
d
d
d是
a
x
ax
ax和
b
y
by
by的因子),那么我可以改写成
a
x
=
t
d
ax = td
ax=td,
b
y
=
p
d
by = pd
by=pd,其中
t
t
t与
p
p
p均为因子!
a
x
=
t
d
(1)
ax = td \tag{1}
ax=td(1)
b
y
=
p
d
(2)
by = pd \tag{2}
by=pd(2)
我们将(1) + (2)可得:
a
x
+
b
y
=
d
(
t
+
p
)
(3)
ax+by=d(t+p) \tag{3}
ax+by=d(t+p)(3)
由此我们可以得出
d
∣
a
x
+
b
y
d|ax+by
d∣ax+by,因为
(
t
+
p
)
(t +p)
(t+p)是因子,所以
d
d
d是
a
x
+
b
y
ax+by
ax+by的因子。(这个性质我们一会再用)
欧几里得算法:gcd(a, b) = gcd(b, a % b)
证明如下:
假设
d
d
d是
a
a
a,
b
b
b的公约数那么则有
d
∣
a
d|a
d∣a,
d
∣
b
d|b
d∣b。
a % b
实际上是
a
−
a
b
∗
b
a-\frac{a}{b}*b
a−ba∗b,令
a
b
=
c
\frac{a}{b} = c
ba=c,则原式可以改写成
a
−
c
∗
b
a-c*b
a−c∗b,根据我们刚开始推到出来的性质,可以得到
d
∣
a
−
c
∗
b
d|a-c*b
d∣a−c∗b,所以gcd(a, b) = gcd(b, a % b)
。
相关题目:acwing 872.最大公约数
#include <iostream>
using namespace std;
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
int n;
scanf("%d", &n);
while(n -- )
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", gcd(a, b));
}
return 0;
}