1.数论总结(本篇章是学习中写的,忘记了便于查找)
整除
若整数b除以非零整数a,商为整数,且余数 [1] 为零,b为被除数,a为除数,即 a|b(“|”是整除符号),读作“a整除b”或“b能被a整除”。a叫做b的约数(或因数),b叫做a的倍数。整除属于除尽的一种特殊情况。
最大公约数
定义
a
a
a,
b
b
b的公共的约数中最大的约数,记作
g
c
d
(
a
,
b
)
gcd ( a , b )
gcd(a,b) ;
最小公倍数
定义
两个或多个整数公有的倍数叫做它们的公倍数,其中除0以外最小的一个公倍数就叫做这几个整数的最小公倍数,
a
a
a,
b
b
b的最小公倍数计算公式:
l
c
m
(
a
,
b
)
=
a
∗
b
g
c
d
(
a
,
b
)
(
注:
a
与
b
的最小公倍数是
a
与
b
的乘积除以
a
与
b
的最大公约数
)
lcm(a,b)=\frac{a*b}{gcd(a,b)}\quad{(注:a与b的最小公倍数是a与b的乘积除以a与b的最大公约数)}
lcm(a,b)=gcd(a,b)a∗b(注:a与b的最小公倍数是a与b的乘积除以a与b的最大公约数);
同余
定义
数学上,同余(英语:congruence modulo,符号:≡)是数论中的一种等价关系。当两个整数除以同一个正整数,若得相同余数,则二整数同余。同余是抽象代数中的同余关系的原型。最先引用同余的概念与“≡”符号者为德国数学家高斯。对于模n同余的所有整数组成的这个集合称为同余类(congruence class或residue class)。
同余符号
两个整数
a
a
a,
b
b
b,若它们除以正整数
m
m
m所得的余数相等,则称
a
a
a,
b
b
b对于模
m
m
m同余。
记作
a
≡
b
(
m
o
d
m
)
a \equiv b (mod\;m)
a≡b(modm);
读作
a
a
a同余于模
m
m
m,或读作
a
a
a与
b
b
b关于模
m
m
m同余。
比如
26
≡
14
(
m
o
d
12
)
26 \equiv 14 (mod\;12)
26≡14(mod12);
同余于的符号是同余相等符号≡。统一码值为 U+2261。但因为方便理由,人们有时会把它(误)写为普通等号 (=);
自反性
a
≡
a
(
m
o
d
m
)
a \equiv a (mod\;m)
a≡a(modm);
对称性
b
≡
a
(
m
o
d
m
)
b \equiv a (mod\;m)
b≡a(modm);
传递性
如果
a
≡
b
(
m
o
d
m
)
a \equiv b (mod\;m)
a≡b(modm)且
b
≡
c
(
m
o
d
m
)
b \equiv c (mod\;m)
b≡c(modm)则
a
≡
c
(
m
o
d
m
)
a \equiv c (mod\;m)
a≡c(modm);
同余式相加
如果
a
≡
b
(
m
o
d
m
)
a \equiv b (mod\;m)
a≡b(modm)且
c
≡
d
(
m
o
d
m
)
c \equiv d (mod\;m)
c≡d(modm)则
a
±
b
≡
c
±
d
(
m
o
d
m
)
a±b \equiv c±d (mod\;m)
a±b≡c±d(modm);
同余式相乘
如果
a
≡
b
(
m
o
d
m
)
a \equiv b (mod\;m)
a≡b(modm)且
c
≡
d
(
m
o
d
m
)
c \equiv d (mod\;m)
c≡d(modm)则
a
b
≡
c
d
(
m
o
d
m
)
ab \equiv cd (mod\;m)
ab≡cd(modm);
同余式相除
如果
a
c
≡
b
c
(
m
o
d
m
/
g
c
d
(
c
,
m
)
)
,
c
≠
0
ac \equiv bc (mod\;m/gcd(c,m)),c\neq0
ac≡bc(modm/gcd(c,m)),c=0则
a
≡
b
(
m
o
d
m
)
a \equiv b (mod\;m)
a≡b(modm);
同余式的幂
如果
a
≡
b
(
m
o
d
m
)
a \equiv b (mod\;m)
a≡b(modm)则
a
n
≡
b
n
(
m
o
d
m
)
a^n \equiv b^n (mod\;m)
an≡bn(modm);
欧拉函数
定义:
在数论,对正整数n,欧拉函数是小于n的正整数中与n互质的数的数目。
(互质是公约数只有1的两个整数,叫做互质整数。公约数只有1的两个自然数,叫做互质自然数,后者是前者的特殊情形)。
列如:
ϕ
(
8
)
=
4
\phi (8) = 4
ϕ(8)=4,即与8的互质数有1,3,5,7,共四个互质数。
标准分解式
x
=
p
1
n
1
∗
p
2
n
2
∗
p
3
n
3
∗
.
.
.
∗
p
m
n
m
(
注:
p
为
x
的互质数
)
x=p_{1}^{n_{1}}*p_{2}^{n_{2}}*p_{3}^{n_{3}}*...*p_{m}^{n_{m}}{(注:p为x的互质数)}
x=p1n1∗p2n2∗p3n3∗...∗pmnm(注:p为x的互质数);
欧拉函数计算式:
ϕ
(
x
)
=
x
∗
(
1
−
1
p
1
)
∗
(
1
−
1
p
2
)
∗
(
1
−
1
p
3
)
∗
.
.
.
∗
(
1
−
1
p
m
)
\phi(x)=x*(1-\frac{1}{p_{1}})*(1-\frac{1}{p_{2}})*(1-\frac{1}{p_{3}})*...*(1-\frac{1}{p_{m}})
ϕ(x)=x∗(1−p11)∗(1−p21)∗(1−p31)∗...∗(1−pm1);
例:
如2022的标准分解式为:
2022=
2
1
∗
3
1
∗
33
7
1
2^1*3^1*337^1
21∗31∗3371;
ϕ
(
2022
)
=
2022
∗
(
1
−
1
2
)
∗
(
1
−
1
3
)
∗
(
1
−
1
337
)
)
=
672
\phi(2022)=2022*(1-\frac{1}{2})*(1-\frac{1}{3})*(1-\frac{1}{337}))=672
ϕ(2022)=2022∗(1−21)∗(1−31)∗(1−3371))=672;
代码附着与验证计算:
//main.cpp
#include <iostream>
int CalculateCoprime()
{
int n;
std::cout << "请输入需要计算的n的互质个数:";
std::cin >> n;
int cout = n;
for (int i = 2; i < n; i++)
{
if (n % i == 0)
{
cout = cout / i * (i - 1);
while (n % i == 0)
{
n /= i;
}
}
}
if (n > 1)
{
cout = cout / n * (n - 1);
}
std::cout << "n的互质个数有" << cout << "个" << std::endl;
system("pause");
return cout;
}
int main()
{
return CalculateCoprime();
}
整除分块
学习莫比乌斯反演,你会发现整除分块这个东西几乎是非常必要的,它可以把一些需要时间复杂度为 O ( n ) O(n) O(n)的枚举优化到 O n O\sqrt n On;
整除分块是数论中常用的一种技巧,先从下面问题入手:
已知 f ( n ) = ∑ i = 1 n ⌊ n i ⌋ 注 : ∑ 为求和符号, ⌊ ⌋ 为向下取整 , ⌈ ⌉ 为向上取整 f(n) = \sum_{i = 1}^{n} \left \lfloor \frac{n}{i} \right \rfloor{ \quad注:\sum为求和符号,\lfloor\;\rfloor为向下取整, \lceil\;\rceil为向上取整} f(n)=∑i=1n⌊in⌋注:∑为求和符号,⌊⌋为向下取整,⌈⌉为向上取整,给定一个 n,求 f(n) 的值;
如果 n n n的取值范围是 1 ⩽ n ⩽ 1 0 6 1 \leqslant n \leqslant 10^6 1⩽n⩽106,毫无疑问,我们会采用直接遍历的方式去计算当前求和的值,便能得到我们的答案,它的时间复杂度是 O ( n ) O(n) O(n),
但如果 1 ⩽ n ⩽ 1 0 9 1 \leqslant n \leqslant 10^9 1⩽n⩽109 ,甚至 1 ⩽ n ⩽ 1 0 20 1 \leqslant n \leqslant 10^{20} 1⩽n⩽1020呢,显然 O ( n ) O(n) O(n)的时间复杂度就完全不能满足我们当前的需求了,这时候我们就得考虑一种新的方式去计算,让代码在时间复杂度上稍微小一点,这就不得不提一提我们整除分块的内容了。
设 n = 20 n=20 n=20, ⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor ⌊in⌋ 的值:
i i i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor ⌊in⌋ | 20 | 10 | 6 | 5 | 4 | 3 | 2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
仔细观测我们可以发现,在 ⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor ⌊in⌋在某些区间一段它的值其实是一致的,那我没是不是可以按照这些区间将它分成不同的块来进行计算呢,这就是整除分块的核心思想了。
既然分块,那块的边界是啥呢?
我们设左端点是
l
l
l,右端点是
r
r
r,
∈
[
l
,
r
]
\in[l,r]
∈[l,r],这一块的个数为
k
k
k,可以知道
k
=
⌊
n
i
⌋
=
⌊
n
l
⌋
k= \lfloor ni\rfloor= \lfloor nl\rfloor
k=⌊ni⌋=⌊nl⌋,而
r
=
m
a
x
(
i
)
r=max(i)
r=max(i),当
(
i
∗
k
<
=
n
)
(i∗k<=n)
(i∗k<=n),所以
i
<
=
n
k
i<=nk
i<=nk,即
r
=
⌊
n
⌊
n
l
⌋
⌋
r= \lfloor n\lfloor nl\rfloor\rfloor
r=⌊n⌊nl⌋⌋。
上代码:
int division_block(int n)
{
int res = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
res += n / l * (r - l + 1);
}
return res;
}
快速幂
快速幂算法的核心思想就是每一步都把指数分成两半,而相应的底数做平方运算。这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也变小,而最后表示的结果却一直不会变。
3
10
=
3
∗
3
∗
3
∗
3
∗
3
∗
3
∗
3
∗
3
∗
3
∗
3
3^{10}=3*3*3*3*3*3*3*3*3*3
310=3∗3∗3∗3∗3∗3∗3∗3∗3∗3
3
10
=
(
3
∗
3
)
∗
(
3
∗
3
)
∗
(
3
∗
3
)
∗
(
3
∗
3
)
∗
(
3
∗
3
)
3^{10}=(3*3)*(3*3)*(3*3)*(3*3)*(3*3)
310=(3∗3)∗(3∗3)∗(3∗3)∗(3∗3)∗(3∗3)
3
10
=
(
3
∗
3
)
5
3^{10}=(3*3)^5
310=(3∗3)5
3
10
=
9
5
3^{10}=9^5
310=95
9
5
=
(
9
4
)
∗
(
9
1
)
9^5=(9^4)*(9^1)
95=(94)∗(91)
9
5
=
(
656
1
1
)
∗
(
9
1
)
9^5=(6561^1)*(9^1)
95=(65611)∗(91)
以下以求a的b次方来介绍 [1]
把b转换成二进制数。
该二进制数第i位的权为
2
i
−
1
2^{i-1}
2i−1
例如:
a
11
=
a
2
0
+
2
1
+
2
3
a^{11}=a^{2^0+2^1+2^3}
a11=a20+21+23
11的二进制是1011
11
=
2
3
×
1
+
2
2
×
0
+
2
1
×
1
+
2
0
×
1
11 = 2^3×1+2^2×0+2^1×1+2^0×1
11=23×1+22×0+21×1+20×1
则:
a
11
=
a
2
3
×
1
+
2
2
×
0
+
2
1
×
1
+
2
0
×
1
=
a
2
3
+
2
1
+
2
0
=
a
2
3
×
a
2
1
×
a
2
0
a^{11}=a^{2^3×1+2^2×0+2^1×1+2^0×1}=a^{2^3+2^1+2^0}=a^{2^3}×a^{2^1}×a^{2^0}
a11=a23×1+22×0+21×1+20×1=a23+21+20=a23×a21×a20
常规求幂
int pow1(int a,int b)
{
int r=1;
while(b--) r*=a;
return r;
}
快速求幂(一般)
int pow2(int a,int b)
{
int r=1,base=a;
while(b!=0)
{
if(b%2) r*=base;
base*=base;
b/=2;
}
return r;
}
快速求幂 (递归)
int f(int m,int n)
{
if(n==1) return m;
int temp=f(m,n/2);
return (n%2==0 ? 1 : m)*temp*temp;
}
快速求幂(位运算)
int pow3(int x, int n)
{
if(n == 0) return 1;
int t = 1;
while(n != 0)
{
if(n & 1) t *= x;
n >>= 1;
x *= x;
}
return t;
}
逆元
若在mod p意义下,对于一个整数a,有
a
∗
b
≡
1
(
m
o
d
p
)
a*b\equiv1(mod\;p)
a∗b≡1(modp),那么我们称呼此时的
b
b
b为
a
a
a关于1模的乘法逆元,同时a也为b的乘法逆元;一个数有逆元的充分必要条件是
g
c
d
(
a
,
p
)
=
1
gcd(a,p)=1
gcd(a,p)=1,此时
a
a
a才有对
p
p
p的乘法逆元。逆元的数学符号是
i
n
v
inv
inv,我们记此时的
b
b
b为
i
n
v
(
a
)
inv(a)
inv(a)或者
a
−
1
a^{-1}
a−1。
例:
5
×
3
≡
1
(
m
o
d
14
)
5×3\equiv1(mod\;14)
5×3≡1(mod14),我们称此时的3为5关于1模14的乘法逆元。
逆元的存在的意义是用来处理除法运算取余问题。
比如:
(
a
÷
b
)
%
p
=
(
a
%
p
÷
b
%
p
)
%
p
(a ÷ b) \% p = (a\%p ÷ b\%p) \%p
(a÷b)%p=(a%p÷b%p)%p
这种写法就是错误的,而这里的除法运算如果换算成加减乘,那么一样的是成立的,比如:
(
a
×
b
)
%
p
=
(
a
%
p
×
b
%
p
)
%
p
(a × b) \% p = (a\%p × b\%p) \%p
(a×b)%p=(a%p×b%p)%p
这样写等号俩边是成立的,但是除法却不行;
在计算过程中,除法可能会造成较大的精度损失,因此,我们一般会把
(
a
÷
b
)
%
p
(a ÷ b) \% p
(a÷b)%p转换成:
(
a
∗
i
n
v
(
b
)
)
%
p
=
(
a
%
p
∗
i
n
v
(
b
)
%
p
)
%
p
(a * inv(b) ) \% p = (a \% p * inv(b) \% p) \% p
(a∗inv(b))%p=(a%p∗inv(b)%p)%p
这样就解决了除法取余不能分开计算的悲哀。但是a,p要满足俩者互质,也就是
g
c
d
(
a
,
p
)
=
1
gcd(a,p)=1
gcd(a,p)=1,a才能找到关于p的逆元。
如何计算逆元?
费马小定理:
当有俩数
a
a
a,
p
p
p满足
g
c
d
(
a
,
p
)
=
1
gcd(a,p)=1
gcd(a,p)=1,p是质数时,则
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1}≡1(mod\;p)
ap−1≡1(modp)
变化一下定理可得:
则
a
∗
a
p
−
2
≡
1
(
m
o
d
p
)
a*a^{p-2}≡1(mod\;p)
a∗ap−2≡1(modp),这时候,我们再看看逆元中的例子,是不是很眼熟。
所以,我们可以使用快速幂求出
a
p
−
2
a^{p-2}
ap−2,即求出
a
a
a的逆元。
附代码:
#include<iostream>
using namespace std;
const int MaxN = 100010;
const int Mod = 1e5 + 7;
__int64 Finv[MaxN];
__int64 Quickpow(__int64 x, __int64 p, __int64 m)//快速幂算法
{
__int64 res = 1;
while (p)
{
if (p & 1) res = res * x % m;
x = x * x % m;
p >>= 1;
}
return res;
}
void Init()//初始化求取逆元
{
Finv[1] = 1;
for (int i = 2; i < MaxN; i++)
{
Finv[i] = Quickpow(i, Mod - 2, Mod);
}
}
int main()
{
Init();
for (int i = 1; i < MaxN; i++)
{
cout << Finv[i] << " ";
}
return 0;
}
拓展欧几里得算法
求a关于1模P的逆元 a ≡ 1 ( m o d P ) a\equiv 1(mod P) a≡1(modP),可以转化为 a ∗ X = 1 + K ∗ P a*X = 1+K*P a∗X=1+K∗P,其中 X X X与 P P P都是整数,这样 X X X即为所求逆元。
这样就可以转化为拓展欧几里得算法(要求 a a a与 P P P互质): a ∗ X + K Y = 1 ( 这里的 K 代指系数,千万别问为什么 a*X + KY = 1\;(这里的K代指系数,千万别问为什么 a∗X+KY=1(这里的K代指系数,千万别问为什么aX = 1+KP 移项会变成 移项会变成 移项会变成a*X + KY = 1 ,俩种在俩个式子里面都代指系数 ) ,俩种在俩个式子里面都代指系数) ,俩种在俩个式子里面都代指系数)。