一.整除的定义.
整除:对于两个自然数 a , b a,b a,b,若存在一个自然数 x x x满足 a x = b ax=b ax=b,则称 a a a整除 b b b,记为 a ∣ b a|b a∣b.
显然 { N , ∣ } \{N,|\} {N,∣}构成一个偏序集.
因数(约数):对于一个自然数 n n n,任意 a a a满足 a ∣ n a|n a∣n都称为 n n n的因数.
倍数:对于一个自然数
n
n
n,任意
a
a
a满足
n
∣
a
n|a
n∣a都称为
n
n
n的倍数.
二.约数函数.
约数个数函数:定义约数个数函数
τ
(
n
)
\tau(n)
τ(n)为:
τ
(
n
)
=
∑
d
∣
n
1
\tau(n)=\sum_{d|n}1
τ(n)=d∣n∑1
结论: τ ( n ) \tau(n) τ(n)的数值一般是在 O ( n 1 3 ) O(n^{\frac{1}{3}}) O(n31)级别的.
约数和函数:定义约数和函数
σ
(
n
)
\sigma(n)
σ(n)为:
σ
(
n
)
=
∑
d
∣
n
d
\sigma(n)=\sum_{d|n}d
σ(n)=d∣n∑d
根据定义可以在 O ( n ) O(\sqrt{n}) O(n)的时间内求解单个 τ ( n ) \tau(n) τ(n)和 σ ( n ) \sigma(n) σ(n).
代码如下:
int Get_tau(int n){
int res=0;
for (int i=1;i*i<=n;++i)
if (n%i==0) res+=2-(i*i==n);
return res;
}
LL Get_sigma(int n){
LL res=0;
for (int i=1;i*i<=n;++i)
if (n%i==0) res+=i+n/i-i*(i*i==n);
return res;
}
三.约数定理与线性筛约数函数.
约数个数定理:设唯一分解
n
=
∏
i
=
1
k
p
i
c
i
n=\prod_{i=1}^{k}p_i^{c_i}
n=∏i=1kpici,那么有:
τ
(
n
)
=
∏
i
=
1
k
(
c
i
+
1
)
\tau(n)=\prod_{i=1}^{k}(c_i+1)
τ(n)=i=1∏k(ci+1)
约数和定理:设唯一分解
n
=
∏
i
=
1
k
p
i
c
i
n=\prod_{i=1}^{k}p_i^{c_i}
n=∏i=1kpici,那么有:
σ
(
n
)
=
∏
i
=
1
k
∑
j
=
0
c
i
p
i
j
\sigma(n)=\prod_{i=1}^{k}\sum_{j=0}^{c_i}p_i^{j}
σ(n)=i=1∏kj=0∑cipij
有了这两个定理,我们可以用线性筛在 O ( n ) O(n) O(n)的时间内筛出 [ 1 , n ] [1,n] [1,n]中所有的 τ \tau τ和 σ \sigma σ值.
具体来说,我们同时维护
c
n
t
(
n
)
cnt(n)
cnt(n)和
s
u
m
(
n
)
sum(n)
sum(n)分别表示
n
n
n的仅有最小素因子组成的约数个数和约数和.形式化的,对于唯一分解
n
=
∏
i
=
1
k
p
i
c
i
n=\prod_{i=1}^{k}p_i^{c_i}
n=∏i=1kpici,有:
c
n
t
(
n
)
=
c
1
+
1
s
u
m
(
n
)
=
∑
j
=
0
c
1
p
1
j
cnt(n)=c_1+1\\ sum(n)=\sum_{j=0}^{c_1}p_1^{j}
cnt(n)=c1+1sum(n)=j=0∑c1p1j
那么对于这四个函数,在线性筛过程中分别有:
1.
p
p
p为素数时有:
c
n
t
(
p
)
=
τ
(
p
)
=
2
s
u
m
(
p
)
=
σ
(
p
)
=
p
+
1
cnt(p)=\tau(p)=2\\ sum(p)=\sigma(p)=p+1
cnt(p)=τ(p)=2sum(p)=σ(p)=p+1
2.
p
∤
n
p\nmid n
p∤n且
p
p
p为
n
p
np
np的最小素因子时有:
c
n
t
(
n
p
)
=
2
τ
(
n
p
)
=
2
τ
(
n
)
s
u
m
(
n
p
)
=
p
+
1
σ
(
n
p
)
=
σ
(
n
)
(
p
+
1
)
cnt(np)=2\\ \tau(np)=2\tau(n)\\ sum(np)=p+1\\ \sigma(np)=\sigma(n)(p+1)
cnt(np)=2τ(np)=2τ(n)sum(np)=p+1σ(np)=σ(n)(p+1)
3.
p
∣
n
p|n
p∣n且
p
p
p为
n
p
np
np的最小素因子时有:
c
n
t
(
n
p
)
=
c
n
t
(
n
)
+
1
τ
(
n
p
)
=
τ
(
n
)
c
n
t
(
n
)
c
n
t
(
n
p
)
s
u
m
(
n
p
)
=
s
u
m
(
n
)
p
+
1
σ
(
n
p
)
=
σ
(
n
)
s
u
m
(
n
)
s
u
m
(
n
p
)
cnt(np)=cnt(n)+1\\ \tau(np)=\frac{\tau(n)}{cnt(n)}cnt(np)\\ sum(np)=sum(n)p+1\\ \sigma(np)=\frac{\sigma(n)}{sum(n)}sum(np)
cnt(np)=cnt(n)+1τ(np)=cnt(n)τ(n)cnt(np)sum(np)=sum(n)p+1σ(np)=sum(n)σ(n)sum(np)
于是我们就可以写出线性筛 τ \tau τ和 σ \sigma σ代码:
int n,b[N+9],pr[N+9],cp;
int cnt[N+9],tau[N+9];
LL sum[N+9],sig[N+9];
void Sieve(){
tau[1]=1;sig[1]=1;
for (int i=2;i<=n;++i){
if (!b[i]){
pr[++cp]=i;
cnt[i]=tau[i]=2;
sum[i]=sig[i]=i+1;
}
for (int j=1;j<=cp&&i*pr[j]<=n;++j){
int t=i*pr[j];
b[t]=1;
if (i%pr[j]==0){
cnt[t]=cnt[i]+1;tau[t]=tau[i]/cnt[i]*cnt[t];
sum[t]=sum[i]*pr[j]+1;sig[t]=sig[i]/sum[i]*sum[t];
break;
}
cnt[t]=2;tau[t]=tau[i]<<1;
sum[t]=pr[j]+1;sig[t]=sig[i]*sum[t];
}
}
}
时间复杂度
O
(
n
)
O(n)
O(n).
四.调和级数.
考虑枚举 i = 1 → n i=1\rightarrow n i=1→n,并枚举 i i i所有的在 [ 1 , n ] [1,n] [1,n]上的倍数,即下面这串代码:
for (int i=1;i<=n;++i)
for (int j=1;i*j<=n;++j);
容易发现这串代码的时间复杂度为:
O
(
∑
i
=
1
n
n
i
)
=
O
(
n
∑
i
=
1
n
1
i
)
O\left(\sum_{i=1}^{n}\frac{n}{i}\right)=O\left(n\sum_{i=1}^{n}\frac{1}{i}\right)
O(i=1∑nin)=O(ni=1∑ni1)
这个式子并不是很好直接估计它能跑的数据范围,考虑一下能否转化为一个我们熟知的式子?
事实上,有一个叫调和级数的东西是这样定义的:
H
(
n
)
=
∑
i
=
1
n
1
i
H(n)=\sum_{i=1}^{n}\frac{1}{i}
H(n)=i=1∑ni1
并且这个东西有这样一个性质:
H
(
n
)
≈
ln
n
H(n)\approx \ln n
H(n)≈lnn
证明:
H
(
n
)
=
1
+
1
2
+
1
3
+
1
4
+
1
5
+
1
6
+
1
7
+
1
8
+
⋯
⇒
1
+
1
2
+
1
4
+
1
4
+
1
8
+
1
8
+
1
8
+
1
8
≤
H
(
n
)
≤
1
+
1
2
+
1
2
+
1
4
+
1
4
+
1
4
+
1
4
+
⋯
⇒
H
(
n
)
≈
ln
n
H(n)=1+\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+\frac{1}{5}+\frac{1}{6}+\frac{1}{7}+\frac{1}{8}+\cdots\\ \Rightarrow 1+\frac{1}{2}+\frac{1}{4}+\frac{1}{4}+\frac{1}{8}+\frac{1}{8}+\frac{1}{8}+\frac{1}{8}\leq H(n)\leq 1+\frac{1}{2}+\frac{1}{2}+\frac{1}{4}+\frac{1}{4}+\frac{1}{4}+\frac{1}{4}+\cdots\\ \Rightarrow H(n)\approx \ln n
H(n)=1+21+31+41+51+61+71+81+⋯⇒1+21+41+41+81+81+81+81≤H(n)≤1+21+21+41+41+41+41+⋯⇒H(n)≈lnn
证毕.
当然你也可以直接用积分估计:
∑
i
=
1
n
1
i
≈
∫
1
n
1
x
d
x
=
ln
n
\sum_{i=1}^{n}\frac{1}{i}\approx \int_{1}^{n}\frac{1}{x}\mathrm{d}x=\ln n
i=1∑ni1≈∫1nx1dx=lnn
同时上面的证明过程证明了调和级数是发散的.
也就是说,上面那个循环的复杂度是
O
(
n
H
(
n
)
)
=
O
(
n
log
n
)
O(nH(n))=O(n\log n)
O(nH(n))=O(nlogn)的(复杂度中log的底数关系不大).
五.最大公因数与最小公倍数.
最大公约数:两个正整数 a , b a,b a,b的最大公约数定义为最大的正整数 k k k满足 k ∣ a k|a k∣a且 k ∣ b k|b k∣b,记为 g c d ( a , b ) = k \mathrm{gcd}(a,b)=k gcd(a,b)=k.
最小公倍数:两个正整数 a , b a,b a,b的最小公倍数定义为最小的正整数 k k k满足 a ∣ k a|k a∣k且 b ∣ k b|k b∣k,记为 l c m ( a , b ) = k \mathrm{lcm}(a,b)=k lcm(a,b)=k.
定理1: g c d ( a , b ) = a b l c m ( a , b ) \mathrm{gcd}(a,b)=\frac{ab}{\mathrm{lcm}(a,b)} gcd(a,b)=lcm(a,b)ab.
证明只需要考虑每一个素因子的指数并利用 min { a , b } + max { a , b } = a + b \min\{a,b\}+\max\{a,b\}=a+b min{a,b}+max{a,b}=a+b即可.
拓展到多个数gcd与lcm之间的关系参见容斥原理相关.
六.最大公因数的求解.
如何求解两个数 a , b a,b a,b的最大公因数?最简单粗暴的方法,枚举一个数 a a a的因数,判定是不是 b b b的因数.时间复杂度 O ( a ) O(\sqrt{a}) O(a).
当然我们还有一个更快的方法,不过在介绍它之前先介绍一个gcd的性质.
gcd性质1: g c d ( a , b ) = g c d ( b , a m o d b ) ( a ≥ b ) \mathrm{gcd}(a,b)=\mathrm{gcd}(b,a\,\,mod\,\,b)\,\,(a\geq b) gcd(a,b)=gcd(b,amodb)(a≥b).
证明:
设
a
=
k
b
+
r
a=kb+r
a=kb+r,其中
k
∈
N
,
r
∈
[
0
,
b
)
k\in N,r\in [0,b)
k∈N,r∈[0,b),问题转化为证明
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
r
)
\mathrm{gcd}(a,b)=\mathrm{gcd}(b,r)
gcd(a,b)=gcd(b,r).
设
g
=
g
c
d
(
a
,
b
)
g=\mathrm{gcd}(a,b)
g=gcd(a,b),则有:
g
∣
(
k
b
+
r
)
∧
g
∣
b
⇒
g
∣
r
g|(kb+r)\wedge g|b\Rightarrow g|r
g∣(kb+r)∧g∣b⇒g∣r
设
g
=
g
c
d
(
b
,
r
)
g=\mathrm{gcd}(b,r)
g=gcd(b,r),则有:
g
∣
b
∧
g
∣
r
⇒
g
∣
(
k
b
+
r
)
g|b\wedge g|r\Rightarrow g|(kb+r)
g∣b∧g∣r⇒g∣(kb+r)
即:
g
c
d
(
a
,
b
)
∣
r
∧
g
c
d
(
b
,
r
)
∣
a
⇒
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
r
)
\mathrm{gcd}(a,b)|r\wedge \mathrm{gcd}(b,r)|a\Rightarrow \mathrm{gcd}(a,b)=\mathrm{gcd}(b,r)
gcd(a,b)∣r∧gcd(b,r)∣a⇒gcd(a,b)=gcd(b,r)
证毕.
有了这个性质后,我们就可以通过这个性质的式子不断迭代得到答案了,代码如下:
int gcd(int a,int b){return b?gcd(b,a%b):a;}
这种方法被称为辗转相除法.
根据同余的性质,每次 a a a和 b b b中总有一个数的大小会减半,所以时间复杂度就是 O ( log a + log b ) O(\log a+\log b) O(loga+logb).
同样的,对于一个序列
a
i
a_i
ai的gcd,我们也可以用迭代来求,时间复杂度
O
(
n
log
a
i
)
O(n\log a_i)
O(nlogai).
七.另一种gcd的求解方法.
根据gcd性质1,显然下面这个式子也是成立的:
gcd
(
a
,
b
)
=
gcd
(
b
,
a
−
b
)
\gcd(a,b)=\gcd(b,a-b)
gcd(a,b)=gcd(b,a−b)
那么对于
gcd
(
a
,
b
)
\gcd(a,b)
gcd(a,b),我们可以根据
a
,
b
a,b
a,b的奇偶性来分类讨论:
1.若
a
,
b
a,b
a,b均为偶数,则有
gcd
(
a
,
b
)
=
2
gcd
(
a
2
,
b
2
)
\gcd(a,b)=2\gcd(\frac{a}{2},\frac{b}{2})
gcd(a,b)=2gcd(2a,2b).
2.若
a
a
a为偶数且
b
b
b为奇数,则有
gcd
(
a
,
b
)
=
gcd
(
a
2
,
b
)
\gcd(a,b)=\gcd(\frac{a}{2},b)
gcd(a,b)=gcd(2a,b).
3.若
a
a
a为奇数且
b
b
b为偶数,则有
gcd
(
a
,
b
)
=
gcd
(
a
,
b
2
)
\gcd(a,b)=\gcd(a,\frac{b}{2})
gcd(a,b)=gcd(a,2b).
4.若
a
,
b
a,b
a,b均为奇数,则有
gcd
(
a
,
b
)
=
gcd
(
a
−
b
,
a
)
\gcd(a,b)=\gcd(a-b,a)
gcd(a,b)=gcd(a−b,a).
容易发现时间复杂度仍然是 O ( log a + log b ) O(\log a+\log b) O(loga+logb)的.
代码如下:
int gcd(int a,int b){
if (a<b) swap(a,b);
if (!b) return a;
if (a&1&&b&1) return gcd(a-b,b);
if (a&1) return gcd(a,b>>1);
if (b&1) return gcd(a>>1,b);
return gcd(a>>1,b>>1)<<1;
}
这个算法的好处是它不需要做除法,如果
a
,
b
a,b
a,b都需要用高精度存时,这个算法会有十分明显的优势.
八.gcd的一些性质.
gcd性质2:假设
a
0
=
0
a_0=0
a0=0,那么有:
g
c
d
i
=
1
n
{
a
i
}
=
g
c
d
i
=
1
n
{
a
i
−
a
i
−
1
}
\mathrm{gcd}_{i=1}^{n}\{a_i\}=\mathrm{gcd}_{i=1}^{n}\{a_i-a_{i-1}\}
gcdi=1n{ai}=gcdi=1n{ai−ai−1}
gcd性质3:假设
a
0
=
+
∞
a_0=+\infty
a0=+∞,那么有:
g
c
d
i
=
1
n
{
a
i
}
=
g
c
d
i
=
1
n
{
a
i
m
o
d
a
i
−
1
}
\mathrm{gcd}_{i=1}^{n}\{a_i\}=\mathrm{gcd}_{i=1}^{n}\{a_i\,\,mod\,\,a_{i-1}\}
gcdi=1n{ai}=gcdi=1n{aimodai−1}
gcd性质4:对于任意一个正整数数列
a
i
a_i
ai,定义
g
i
=
g
c
d
j
=
1
i
{
a
j
}
g_i=\mathrm{gcd}_{j=1}^{i}\{a_j\}
gi=gcdj=1i{aj},那么有:
g
i
≠
g
i
+
1
⇒
2
g
i
+
1
≤
g
i
g_i\neq g_{i+1}\Rightarrow 2g_{i+1}\leq g_i
gi=gi+1⇒2gi+1≤gi
九.丢番图方程.
丢番图方程:定义一个方程 ∑ i = 1 n a i x i b i = c \sum_{i=1}^{n}a_ix_i^{b_i}=c ∑i=1naixibi=c为丢番图方程,当且仅当任意 a i , c ∈ Z , b i ∈ N + a_i,c\in Z,b_i\in N_+ ai,c∈Z,bi∈N+且 a i ≠ 0 a_i\neq 0 ai=0,并且我们只讨论丢番图方程的整数解.
线性丢番图方程:满足所有 b i = 1 b_i=1 bi=1的丢番图方程称为线性丢番图方程.
接下来我们主要研究线性丢番图方程.
二元线性丢番图方程:满足
n
=
2
n=2
n=2的线性丢番图方程称为二元线性丢番图的方程.
十.二元线性丢番图方程的求解.
裴蜀定理:二元线性丢番图方程 a x + b y = gcd ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b)必然存在一组整数解.
证明:
首先对于
b
=
0
b=0
b=0的情况,必然存在整数解
x
=
1
,
y
=
0
x=1,y=0
x=1,y=0.
那么对于任意一个方程
a
x
+
b
y
=
gcd
(
a
,
b
)
ax+by=\gcd(a,b)
ax+by=gcd(a,b),必然可以构造出另一个方程:
b
x
′
+
(
a
m
o
d
b
)
y
′
=
gcd
(
b
,
a
m
o
d
b
)
=
gcd
(
a
,
b
)
b
x
′
+
(
a
−
⌊
a
b
⌋
b
)
y
′
=
gcd
(
a
,
b
)
b
x
′
+
a
y
′
−
b
⌊
a
b
⌋
y
′
=
gcd
(
a
,
b
)
a
y
′
+
b
(
x
′
−
⌊
a
b
⌋
y
′
)
=
gcd
(
a
,
b
)
bx'+(a\,\,mod\,\,b)y'=\gcd(b,a\,\,mod\,\,b)=\gcd(a,b)\\ bx'+\left(a-\left\lfloor\frac{a}{b}\right\rfloor b\right)y'=\gcd(a,b)\\ bx'+ay'-b\left\lfloor\frac{a}{b}\right\rfloor y'=\gcd(a,b)\\ ay'+b\left(x'-\left\lfloor\frac{a}{b}\right\rfloor y'\right)=\gcd(a,b)
bx′+(amodb)y′=gcd(b,amodb)=gcd(a,b)bx′+(a−⌊ba⌋b)y′=gcd(a,b)bx′+ay′−b⌊ba⌋y′=gcd(a,b)ay′+b(x′−⌊ba⌋y′)=gcd(a,b)
也就是说,只要方程
b
x
′
+
(
a
m
o
d
b
)
y
′
=
gcd
(
b
,
a
m
o
d
b
)
bx'+(a\,\,mod\,\,b)y'=\gcd(b,a\,\,mod\,\,b)
bx′+(amodb)y′=gcd(b,amodb)有解,必然可以构造方程
a
x
+
b
y
=
gcd
(
a
,
b
)
ax+by=\gcd(a,b)
ax+by=gcd(a,b)的一组解:
x
=
y
′
y
=
x
′
−
⌊
a
b
⌋
y
′
x=y'\\ y=x'-\left\lfloor\frac{a}{b}\right\rfloor y'
x=y′y=x′−⌊ba⌋y′
根据数学归纳法即可证明.
证毕.
裴蜀定理的推论:二元线性丢番图方程 a x + b y = c ax+by=c ax+by=c存在整数解当且仅当 gcd ( a , b ) ∣ c \gcd(a,b)|c gcd(a,b)∣c.
证明:
根据裴蜀定理可以证明充分性.
而我们给等式两边除掉
gcd
(
a
,
b
)
\gcd(a,b)
gcd(a,b)后,等式左边仍然为整数,此时等式右边也必须为整数,所有需要
gcd
(
a
,
b
)
∣
c
\gcd(a,b)|c
gcd(a,b)∣c,必要性得证.
证毕.
根据证明过程,我们还可以写出求一组解的求解算法,称之为扩展欧几里得算法.
代码如下(注意这个代码只能求解 a , b a,b a,b为正的情况,若需要为负可以先把符号交给 x , y x,y x,y):
int Ex_gcd(int a,int b,int &x,int &y){
if (!b) {x=1;y=0;return a;}
int g=Ex_gcd(b,a%b,y,x);
y-=a/b*x;
return g;
}
时间复杂度与求解gcd的时间复杂度相同,为 O ( log a + log b ) O(\log a+\log b) O(loga+logb).
这个算法得到的解一般不会太大,不至于到爆long long的地步.
同时,可以十分容易的证明,若一个二元线性丢番图方程
a
x
+
b
y
=
c
ax+by=c
ax+by=c有一组解
x
0
,
y
0
x_0,y_0
x0,y0,则此二元线性丢番图方程的通解为:
x
=
x
0
+
k
b
gcd
(
a
,
b
)
y
=
y
0
−
k
a
gcd
(
a
,
b
)
x=x_0+k\frac{b}{\gcd(a,b)}\\ y=y_0-k\frac{a}{\gcd(a,b)}
x=x0+kgcd(a,b)by=y0−kgcd(a,b)a
其中
k
k
k是任意整数.
十一.线性丢番图方程的求解.
对于一般线性丢番图方程:
∑
i
=
1
n
a
i
x
i
=
c
\sum_{i=1}^{n}a_ix_i=c
i=1∑naixi=c
一般线性丢番图方程解存在性判定法则:若 g c d i = 1 n { a i } ∣ c \mathrm{gcd}_{i=1}^{n}\{a_i\}|c gcdi=1n{ai}∣c则有整数解,否则无整数解.
同时,对于线性丢番图方程的求解,可以考虑先求出分
n
−
1
n-1
n−1步求解,第
i
i
i步求解出方程:
g
c
d
j
=
1
i
{
a
j
}
x
+
a
i
+
1
x
i
+
1
=
g
c
d
j
=
1
i
+
1
{
a
j
}
\mathrm{gcd}_{j=1}^{i}\{a_j\}x+a_{i+1}x_{i+1}=\mathrm{gcd}_{j=1}^{i+1}\{a_j\}
gcdj=1i{aj}x+ai+1xi+1=gcdj=1i+1{aj}
时间复杂度 O ( n log a i ) O(n\log a_i) O(nlogai).
代码如下(注意这个代码只能求解 a i a_i ai为正的情况,而且这个算法得到解的数值没有上限保证,可能会爆long long之类的):
int Ex_gcd(int a,int b,int &x,int &y){
if (!b) {x=1;y=0;return a;}
int g=Ex_gcd(b,a%b,y,x);
y-=a/b*x;
return g;
}
int t[N+9];
bool Get_root(int *a,int n,int c,int *res){
int g=a[1];
res[1]=t[n]=1;
for (int i=2;i<=n;++i) g=Ex_gcd(g,a[i],t[i-1],res[i]);
if (c%g) return 0;
c/=g;
for (int i=n-1;i>=1;--i) res[i]*=t[i]*=t[i+1];
for (int i=1;i<=n;++i) res[i]*=c;
return 1;
}