数论专题:基础数论
一、基础数论
薄绿天下第一!
1. 质数及筛法
定义:质数为因子只有1和它本身的数。
筛法1 : 朴素筛法
管他是不是质数,枚举就完事er了。(指一个一个找)
代码如下:
#include<bits/stdc++.h>
using namespace std;
bool check(int x){
for(int i=2;i<=sqrt(x);i++)
if(x%i==0) return 0;
return 1;
}
int prime[10000],cnt=0;
void work(int n){
for(int i=2;i<=n;i++)
if(check(i)) prime[++cnt]=i;
}
int main()
{
int n;scanf("%d",&n);
work(n);
for(int i=1;i<=cnt;i++) printf("%d\n",prime[i]);
return 0;
}
时间复杂度肉眼可见的垃圾。
除非数据范围很小或实在是不知道更好的筛法,建议永远不要使用。
筛法2:埃氏筛
按照埃老先生的思想,我们可以把每一个数的倍数(当然1除外)标记为合数,这样剩下的数就全部是质数了,很简单的思想。
代码如下:
#include<bits/stdc++.h>
using namespace std;
int prime[10000],cnt=0;
bool isprime[10000];
void Eratos(int n){
memset(isprime,true,sizeof(isprime));
for(int i=2;i<=n;i++){
if(!isprime[i]) continue;
prime[++cnt]=i;
for(int j=1;j<=n/i;j++) isprime[j*i]=false;
}
}
int main()
{
int n;scanf("%d",&n);
Eratos(n);
for(int i=1;i<=cnt;i++) printf("%d\n",prime[i]);
return 0;
}
现在的时间复杂度是 O(
∑
质
数
p
<
=
N
N
p
\sum_{质数p<=N}\frac N p
∑质数p<=NpN)=O(N
l
o
g
log
log
l
o
g
log
logN)
已经非常接近线性。
但是,欧老先生发现了更好的筛法。
筛法3:欧拉筛(线性筛)
欧拉筛是基于埃氏筛的思想进行了优化。
欧拉筛的核心思想就是高效,不重复的维护
i
s
p
r
i
m
e
isprime
isprime 数组。
思路主要有三点:
- 依次考虑2~N间的每一个数 i i i;
- 若 i s p r i m e [ i ] = = t r u e isprime[i]==true isprime[i]==true 说明 i i i是质数,就把 i i i保存。
- 扫描不大于 i i i的所有质数 p p p,让 i s p r i m e [ i ∗ p ] = = f a l s e isprime[i*p]==false isprime[i∗p]==false,标记合数,并保证要保证 i i i% p ! = 0 p !=0 p!=0,否则不是该合数的最小分解方案,会导致重复。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+100;
bool isprime[N];
int prime[N];
void work(int n){
memset(isprime,true,sizeof(isprime));
prime[0]=0;
for(int i=2;i<=n;i++){
if(isprime[i]){
prime[++prime[0]]=i;
}
for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++){
isprime[i*prime[j]]=false;
if(i%prime[j]==0) break;//非常重要的一句话,优化到线性的关键
}
}
}
void print(){
for(int i=1;i<=prime[0];i++){
printf("%d\n",prime[i]);
}
}
int main()
{
int n;scanf("%d",&n);
work(n);
print();
return 0;
}
现在的时间复杂度是 O(N),已经到了高效的线性时间。
这个是最常用的质数筛法,一定要记住。
除了以上的质数筛法,还有一种名为Miller_rabin的高效随机筛法,感兴趣可以了解一下。
薄绿天下第一!
2. 分解质因数
*前置知识:
算术唯一分解定理:
任何一个大于1的整数N,一定能唯一分解为有限个质数的乘积。
即:
N
=
p
1
c
1
p
2
c
2
…
…
p
m
c
m
N=p_1^{c_1}p_2^{c_2}……p_m^{c_m}
N=p1c1p2c2……pmcm
薄绿天下第一!
结合“试除法”及埃氏筛,可以通过扫描 2 ~
⌊
x
⌋
\lfloor{\sqrt{x}}\rfloor
⌊x⌋ 区间的每一个数
i
i
i,判断是否整除
x
x
x,在确定
i
i
i不是
x
x
x的平方根后,把
i
i
i 与
x
i
\frac x i
ix 都存进分解的数组中。
这样,就可以在 O(
N
\sqrt{N}
N)的时间内完成质因数分解。
代码如下:
#include<bits/stdc++.h>
using namespace std;
int p[1000],times[1000],tot;
void divide(int n){
printf("%d = ",n);
tot=0;memset(p,0,sizeof(p));memset(times,0,sizeof(times));
for(int i=2;i<=sqrt(n);i++){
if(n%i==0){
p[++tot]=i;times[tot]=0;
while(n%i==0) {n/=i;times[tot]++;}
}
}
if(n>1) {p[++tot]=n;times[tot]=1;}
for(int i=1;i<tot;i++){
printf("%d",p[i]);
if(times[i]!=1) printf("^%d",times[i]);
printf(" * ");
}
printf("%d",p[tot]);
if(times[tot]!=1) printf("^%d",times[tot]);
printf("\n");
}
int main()
{
int t;scanf("%d",&t);
while(t--){
int n;scanf("%d",&n);
divide(n);
}
return 0;
}
除了这种方法,还有一种方法叫做Pollard’s Rho,这个算法更高效,感兴趣的可以了解一下。
薄绿天下第一!
3. 一些引理、推论、推广
1.算术唯一分解定理的推论
(1).
N
N
N 的正约数的个数
=
(
c
1
+
1
)
∗
(
c
2
+
1
)
∗
…
…
∗
(
c
3
+
1
)
(c_1+1)*(c_2+1)*……*(c_3+1)
(c1+1)∗(c2+1)∗……∗(c3+1)
=
∏
i
=
1
m
(
c
i
+
1
)
\prod_{i=1}^{m}(c_i+1)
∏i=1m(ci+1)
薄绿天下第一!
(2).
N
N
N 的所有正约数的和
=
(
1
+
p
1
+
p
1
2
+
…
…
+
p
1
c
1
)
∗
…
…
∗
(
1
+
p
m
+
p
m
2
+
…
…
+
p
m
c
m
)
(1+p_1+p_1^2+……+p_1^{c_1})*……*(1+p_m+p_m^2+……+p_m^{c_m})
(1+p1+p12+……+p1c1)∗……∗(1+pm+pm2+……+pmcm)
=
∏
i
=
1
m
(
∑
j
=
0
c
i
(
p
i
)
j
)
\prod_{i=1}^{m}(\sum_{j=0}^{c_i}(p_i)^j)
∏i=1m(∑j=0ci(pi)j)
薄绿天下第一!
2.求N的所有正约数
方法与分解质因数相似,但需要用堆来维护大小顺序,这里不再过多解释。时间复杂度为 O(
x
\sqrt x
x)。
代码如下:
#include<bits/stdc++.h>
using namespace std;
priority_queue<int,vector<int>,greater<int> > q1;//小根堆
void work(int x){
for(int i=1;i<=sqrt(n);i++){
if(x%i==0){
q1.push(i);
if(x/i!=i){q1.push(x/i);}
}
}
while(q1.size()){
printf("%d ",q1.top());
q1.pop();
}
}
int main()
{
int t;scanf("%d",&t);
while(t--){
int n;scanf("%d",&n);
work(n);
printf("\n");
}
return 0;
}
3.试除法的推论
一个整数 N N N 的约数个数最多为 2 N 2\sqrt N 2N 个。
4.倍数法求正约数及推论
如果是筛选1~N中每个数的约数,那用试除法的时间复杂度瞬间爆炸。
所以,我们引入新方法:倍数法。
不如反过来考虑,对于区间内每个数
d
d
d,
1
1
1~
N
N
N 之间以
d
d
d 为约数的数就是所有
d
d
d的约数,于是我们就有了这样反过来从因数找原数的思路。
代码如下:
#include<bits/stdc++.h>
using namespace std;
vector<int> factor[500010];
void work(int n){
for(int i=1;i<=n;i++)
for(int j=1;j<=n/i;j++)
factor[i*j].push_back(i);
for(int i=1;i<=n;i++){
for(int j=0;j<factor[i].size();j++)
printf("%d ",factor[i][j]);
puts("");
}
}
int main()
{
int n;scanf("%d",&n);
work(n);
return 0;
}
薄绿天下第一!
代码的时间复杂度为 O(
N
+
N
/
2
+
N
/
3
+
…
…
+
N
/
N
N+N/2+N/3+……+N/N
N+N/2+N/3+……+N/N)=O(
N
l
o
g
N
NlogN
NlogN)
薄绿天下第一!
推论:1~N每个数的约数个数的总和大约为
N
l
o
g
N
NlogN
NlogN。
5.有关素数的引理
1 1 1~ N N N( N ≤ 2 ∗ 1 0 9 N\leq2*10^9 N≤2∗109) 中任何数的不同质因子都不会超过10个,且所有质因子的质数总和不超过30。
在做一些数据范围判断时很有用。
薄绿天下第一!
4. 最大公约数与最小公倍数
定理:
∀
a
,
b
∈
N
\forall a,b\in N
∀a,b∈N
g
c
d
(
a
,
b
)
∗
l
c
m
(
a
,
b
)
=
a
∗
b
gcd(a,b)*lcm(a,b)=a*b
gcd(a,b)∗lcm(a,b)=a∗b
显然,有了最大公约数,就可以求出已知两数的最小公倍数了。
薄绿天下第一!
计算最大公约数的方法:
1.更相减损术
有如下定理:
∀
a
,
b
∈
N
a
≥
b
\forall a,b\in N a\geq b
∀a,b∈Na≥b,有
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
−
b
)
=
g
c
d
(
a
,
a
−
b
)
gcd(a,b)=gcd(b,a-b)=gcd(a,a-b)
gcd(a,b)=gcd(b,a−b)=gcd(a,a−b)
∀ a , b ∈ N \forall a,b\in N ∀a,b∈N,有 g c d ( 2 a , 2 b ) = 2 g c d ( a , b ) gcd(2a,2b)=2gcd(a,b) gcd(2a,2b)=2gcd(a,b)
代码实现:
int gcd(int a,int b){
while((!a&1)&&(!b&1)) {a>>=1;b>>=1;}
while(a!=b){
if(a>b) a-=b;
else b-=a;
}
return a;
}
2.欧几里得算法
有如下定理:
∀
a
,
b
∈
N
,
b
≠
0
\forall a,b\in N,b\neq 0
∀a,b∈N,b=0,
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
m
o
d
b
)
gcd(a,b)=gcd(b,a mod b)
gcd(a,b)=gcd(b,amodb)
代码实现:
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
薄绿天下第一!
一般来讲,欧几里得算法的效率要比更相减损术高,时间复杂度为 O(
l
o
g
(
a
+
b
)
log(a+b)
log(a+b)),而且代码实现更简单,一般多使用欧几里得求最大公因数。
薄绿天下第一!
5. 互质与欧拉函数
互质的定义: ∀ a , b ∈ N \forall a,b\in N ∀a,b∈N,若 g c d ( a , b ) = 1 gcd(a,b)=1 gcd(a,b)=1,则称 a a a, b b b 互质。
欧拉函数的定义: 1~ N N N中与 N N N 互质的数的个数被称为欧拉函数,记为 ϕ ( N ) \phi(N) ϕ(N)。
欧拉函数的求法:
在算术分解定理中,
N
=
p
1
c
1
p
2
c
2
…
…
p
m
c
m
N=p_1^{c_1}p_2^{c_2}……p_m^{c_m}
N=p1c1p2c2……pmcm,则:
ϕ
(
N
)
=
N
∗
p
1
−
1
p
1
∗
p
2
−
1
p
2
∗
…
…
∗
p
m
−
1
p
m
=
N
∗
∏
质
数
p
∣
N
(
1
−
1
p
)
\phi(N)=N*\frac{p_1-1}{p_1}*{\frac{p_2-1}{p_2}}*……*{\frac{p_m-1}{p_m}}=N*\prod_{质数p\mid N}(1-\frac1 p)
ϕ(N)=N∗p1p1−1∗p2p2−1∗……∗pmpm−1=N∗∏质数p∣N(1−p1)
这里利用了容斥原理的思想,在组合篇会提到。
根据这个式子,发现只要分解了质因数,就能在分解的过程中进行求解欧拉函数。
代码如下:
int phi(int n){
int ans=n;
for(int i=2;i<=sqrt(n);i++)
if(n%i==0){
ans=ans/i*(i-1);
while(n%i==0) n/=i;
}
if(n>1) ans=ans/(n*(n-1);
return ans;
}
欧拉函数的性质:
性质1. ∀ n > 1 \forall n>1 ∀n>1, 1 1 1~ N N N中与 n n n互质的数的和为 n ∗ ϕ ( n ) / 2 n*\phi(n)/2 n∗ϕ(n)/2。
性质2. 若
a
,
b
a,b
a,b 互质,则
ϕ
(
a
,
b
)
=
ϕ
(
a
)
ϕ
(
b
)
\phi(a,b)=\phi(a)\phi(b)
ϕ(a,b)=ϕ(a)ϕ(b)。
薄绿天下第一!
在讨论下面的性质前,先引入积性函数的概念。
积性函数: 当
a
,
b
a,b
a,b 互质时,若有
f
(
a
b
)
=
f
(
a
)
f
(
b
)
f(ab)=f(a)f(b)
f(ab)=f(a)f(b),则称
f
f
f 为积性函数。
那么显然,欧拉函数就是一个积性函数。
积性函数的性质: 在算术基本定理中
n
=
∏
i
=
1
m
p
i
c
i
n=\prod_{i=1}^m {p_i}^{c_i}
n=∏i=1mpici,则
f
(
n
)
=
∏
i
=
1
m
f
(
p
1
c
i
)
f(n)=\prod_{i=1}^mf({p_1}^{c_i})
f(n)=∏i=1mf(p1ci)
性质3. 设 p p p为质数,若 p ∣ n p|n p∣n且 p 2 ∣ n p^2|n p2∣n,则 ϕ ( n ) = ϕ ( n / p ) ∗ p \phi(n)=\phi(n/p)*p ϕ(n)=ϕ(n/p)∗p。
性质4. 设 p p p为质数,若 p ∣ n p|n p∣n但 p 2 ∣ n p^2|n p2∣n,则 ϕ ( n ) = ϕ ( n ∣ p ) ∗ ( p − 1 ) \phi(n)=\phi(n|p)*(p-1) ϕ(n)=ϕ(n∣p)∗(p−1)。
性质5. ∑ d ∣ n ϕ ( d ) = n \sum_{d|n}{\phi(d)}=n ∑d∣nϕ(d)=n
利用如上的性质,可以有如下的求法。
方法1. 利用埃氏筛法,按照欧拉函数的计算式计算。
void euler(int n){
for(int i=2;i<+n;i++) phi[i]=i;
for(int i=2;i<+n;i++)
if(phi[i]==i)
for(int j=i;j<=n;j+=i)
phi[j]=phi[j]/i*(i-1);
}
这个算法的时间复杂度为 O( N l o g N NlogN NlogN)。相对来讲效率不高。
方法2. 利用线性筛法,从 ϕ ( n / p ) \phi(n/p) ϕ(n/p) 递推到 ϕ ( n ) \phi(n) ϕ(n)进行求解。
int v[MAX_N],prime[MAX_N],phi[MAX_N];
void euler(int n){
memset(v,0,sizeof(v));
int cnt=0;
for(itn i=2;i<=n;i++){
if(v[i]==0){
v[i]=i,prime[++cnt]=i;
phi[i]=i-1;
}
for(int j=1;j<=m;j++){
if(prime[j]>v[i]||prime[j]>n/i) break;
v[i*prime[j]]=prime[j];
phi[i*prime[j]]=phi[i]*(i%prime[j]?prime[j]-1:prime[j]);
}
}
}
薄绿天下第一!
6. 同余
定义: 若整数 a a a 和整数 b b b 除以正整数 m 的余数相等,则称 a , b a,b a,b 模 m m m 同余,记为 a ≡ b m o d ( m ) a\equiv b\mod(m) a≡bmod(m)。
前置知识:
1.欧拉定理
若正整数
a
,
n
a,n
a,n 互质,则
a
ϕ
(
n
)
≡
1
m
o
d
(
n
)
a^{\phi(n)}\equiv 1\mod(n)
aϕ(n)≡1mod(n)。
2.费马小定理
若
p
p
p 是质数,则对于任意的整数
a
a
a ,有
a
p
≡
a
m
o
d
(
p
)
a^p\equiv a\mod(p)
ap≡amod(p)。
证明:
可以发现,把式子变换为
a
p
−
1
≡
1
m
o
d
(
p
)
a^{p-1}\equiv 1\mod(p)
ap−1≡1mod(p) 后,就是欧拉定理中当
n
n
n 是质数的一种特殊情况。(非常妙)
3.欧拉定理的推论
若正整数
a
,
n
a,n
a,n 互质,则对于任意正整数
b
b
b ,有
a
b
≡
a
b
m
o
d
ϕ
(
n
)
m
o
d
(
n
)
a^b\equiv a^{b mod \phi(n)}\mod(n)
ab≡abmodϕ(n)mod(n)
证明:
设
b
=
q
∗
ϕ
(
n
)
+
r
b=q*\phi(n)+r
b=q∗ϕ(n)+r ,其中
0
≤
r
<
ϕ
(
n
)
0\leq r<\phi(n)
0≤r<ϕ(n) ,即
r
=
b
m
o
d
ϕ
(
n
)
r=b \mod\phi(n)
r=bmodϕ(n)。 便有:
a
b
≡
a
q
∗
ϕ
(
n
)
+
r
a^b\equiv a^{q*\phi(n)+r}
ab≡aq∗ϕ(n)+r
因为同余式右边
=
(
a
ϕ
(
n
)
)
q
∗
a
r
≡
a
r
m
o
d
(
n
)
=(a^{\phi(n)})^q*a^r \equiv a^r\mod(n)
=(aϕ(n))q∗ar≡armod(n)
所以可得
a
b
≡
a
b
m
o
d
ϕ
(
n
)
m
o
d
(
n
)
a^b\equiv a^{b mod \phi(n)}\mod(n)
ab≡abmodϕ(n)mod(n)
特别地,当 a , n a,n a,n 不一定互质且 b > ϕ ( n ) b>\phi(n) b>ϕ(n) 时,有 a b ≡ a b m o d ϕ ( n ) + ϕ ( n ) m o d ( n ) a^b\equiv a^{b\mod\phi(n)+\phi(n)}\mod(n) ab≡abmodϕ(n)+ϕ(n)mod(n)
4.裴蜀定理(Bézout)
对于任意整数
a
,
b
a,b
a,b,存在一对整数
x
,
y
x,y
x,y,满足
a
x
+
b
y
=
gcd
(
a
,
b
)
ax+by=\gcd(a,b)
ax+by=gcd(a,b)
对于
x
=
1
,
y
=
0
x=1,y=0
x=1,y=0的情况时显然成立的,更一般的证明要用到欧几里得算法递归过程的数学归纳。
更一般情况的证明
相关知识和算法:
1.扩展欧几里得算法
扩展欧几里得算法的核心是裴蜀定理,利用欧几里得算法的递归流程进行
a
x
+
b
y
=
gcd
(
x
,
y
)
ax+by=\gcd(x,y)
ax+by=gcd(x,y) 方程的特解
x
0
,
y
0
x_0,y_0
x0,y0 求解。
定义变量
x
0
,
y
0
,
d
x_0,y_0,d
x0,y0,d,分别表示特解和
gcd
(
a
,
b
)
\gcd(a,b)
gcd(a,b),通过如下程序求解。
int exgcd(int a,int b,int &x,int &y){ //int 类型版本
if(b==0) {x=1,y=0;return a;}// 求gcd(a,b),得到第一步特解x=1,y=0;
int d=exgcd(b,a%b,x,y);
int z=x;
x=y;y=z-y*(a/b);
return d;
}
为薄绿献上心脏!
7. 名人名言
数论很简单,就是头冷。——数论之神yky
贪心做数论,暴力做组合。——chunzhen
暴力拿国二,贪心拿国一。——神lyc
*未经作者允许禁止转载