18年讲了一遍oi数论,上学期听了两遍初等数论,这学期又听了一遍,马上又要讲,于是写了一份讲义。写的时候感觉要把一个脑子里知道的结构描述出来真的很难,有时候句子都写不通顺了。
1 整数集上的整除关系
1.1 定义
对于(a, b in mathbb{Z}),如果存在(c in mathbb{Z}),使(a=bc),则说(b)整除(a),记作(b|a),并称(a)是(b)的一个倍数,(b)是(a)的一个约数. 下文的大部分讨论是在非负整数集(mathbb{N})上进行的.
1.2 性质
在这里仅指出一些比较常用的性质:
- 整除具有传递性,如果(a|b, b|c),则有(a|c)
- 任何整数都能整除(0)
- 如果(a|b)且(a|c),那么(a)也能整除(b)和(c)的任何整倍数组合,即(forall x, y in mathbb{Z}, a|(bx+cy))
1.3 约数
如果(d)是(a)的一个约数,那么(frac{a}{d})也是(a)的一个约数. 当(d<sqrt{a})时,(frac{a}{d}>sqrt{a}),反之亦然. 因此我们看到,一个正整数(a)的约数中,小于(sqrt{a})的部分和大于(sqrt{a})的部分是成对出现的,每一对(d_1, d_2)都满足(d_1d_2=a)
还有一个特殊情况:如果(a)是完全平方数,(sqrt{a})本身也是(a)的一个约数,它与自己成对出现.
到这里容易观察到约数的一些性质:(a)的约数数量不超过(2sqrt{a})——当然这是一个很松的上界. 非完全平方数的约数可以被完美地划分为一一对应的两部分,因此有偶数个约数. 而完全平方数会多出一个(sqrt{a})作为约数,因此有奇数个约数.
1.4 素数
如果一个正整数仅有自身和(1)两个约数,则称为素数. 在OI中,我们首先关心的是怎样判断一个数是否是素数,以及找出某个范围内的所有素数.
如果(n)是合数,那么它一定有至少一个不超过(sqrt{n})且不是1的约数. 因此素性检测最朴素的做法是在([2, sqrt{n}])中枚举并试除即可,复杂度为(O(sqrt{n})). 在(n)更大时,OI中常用的算法是Miller-Rabin素性测试,如果有兴趣可以自行了解. 素性测试是一个经典的算法问题,学术界在这一问题上也有许多已有的成果,不过大多由于过于复杂,OI中并不涉及.
给定一个上界(n),筛法可以用来求出不超过(n)的所有素数. 为范围内的每个整数置一个标记,初始为false
,枚举([2, sqrt{n}])中的所有整数(d),再将(d)的所有不超过(n)的倍数的标记置为true
. 这样一来,所有合数都会被筛掉. 略去取整,它的时间代价是:
由于调和级数在(n ightarrow infin)时有近似(H_n sim ln n),这一筛法的时间复杂度为(O(n log n))
注意到在枚举(d)时,作为合数的(d)是没有意义的,因为被它筛掉的数一定也会被(d)的某个质因子筛掉,因此可以仅在(d)是质数时对它的倍数进行枚举,这被称为埃氏筛法,时间复杂度改进到(O(n log log n))
这一筛法依然是有浪费的,因为一个整数可能会被它的多个质因子重复筛掉. 欧拉筛让每个合数在它的最小质因子处被筛掉,从而达到了(O(n))的时间复杂度. 它的算法过程如下:
- 将[2, n]的所有标记置
false
,维护一个质数列表,初始为空 - 从(2)到(n)枚举(i)
- 如果(i)的标记依然是
false
,则将(i)加入质数列表的末尾 - 从前向后枚举质数列表中的质数(p_j)
- 如果(i cdot p_j)过大(超过(n))则停止
- 将(i cdot p_j)标记为
true
- 如果(p_j|i),则停止. 这意味着此时(p_j)是(i)的最小质因数,而在这之前(包括这一个),(p_j)都是(i cdot p_j)的最小质因数.
- 如果(i)的标记依然是
由于每个合数仅会在(p_j)取它的最小质因子时被作为(i cdot p_j)枚举到,内层枚举的次数也不超过(n),时间复杂度是(O(n)).
std::vector<int> primes;
for(int i = 2; i <= n; ++i)
{
if(!vis[i]) primes.push_back(i);
for(size_t j = 0; j < primes.size(); ++j)
{
int cur = i * primes[j];
if(cur > n) break;
vis[cur] = true;
if(i % primes[j] == 0) break;
}
}
1.5 带余除法
对于(a, b in mathbb{Z}, b>0),总可以找到恰好一组(q, r in mathbb{Z}),使得
(q)被称为(a)除以(b)的商,(r)是余数.
用(a)除以(b)求余数的运算称作取模,记为(r=a mod b).
(q)是对(frac{a}{b})向下取整的结果,即(q=lfloor frac{a}{b} floor),因此取模运算满足(a mod b=a-lfloor frac{a}{b} floor b)
1.6 算术基本定理
我们不加证明地给出算术基本定理:任何正整数都可以唯一地表示成一组非降素数的乘积. 例如(60=2 imes 2 imes 3 imes 5). 一般我们将同一个质因子合并,写成(n=prod_i p_i^{q_i})的形式.
这个定理可以帮助我们了解许多(mathbb{Z})的性质. 如果我们将那些在乘积中没有出现的素数也列出来,并把它的次数记为(0),则任何一个正整数都可以写成下面的形式:
这是一个无穷乘积,但其中仅有有限多个(q_i)是非零的. 这样一来,任何一个(n in mathbb{Z})都对应着(q_1, q_2, dots)这样一个无穷序列. 两个整数相乘对应着两个序列对应位置的相加.
在处理整除相关的问题时,这种看法有时会更加方便. 例如,如果(a|b),则(a)的指数序列上对应位置总是小于等于(b). 这样也容易发现,如果枚举(n)的所有约数,就等价于在每个(q_i)处让约数的指数取遍(0)到(q_i). 因此(n)的约数数量(sigma_0(n)=prod_i(q_i+1))
在进行质因子分解时,考虑到(n)至多有一个大于(sqrt{n})的质因子,朴素算法容易降低到(O(sqrt{n}))
std::vector<std::pair<int, int> > factors;
for(int i = 2; i * i <= n; ++i)
{
if(n % i == 0)
{
int cnt = 0;
while(n % i == 0)
{
n /= i;
++cnt;
}
factors.push_back(std::make_pair(i, cnt));
}
}
if(n > 1) factors.push_back(std::make_pair(n, 1));
1.7 最大公约数,最小公倍数
将(a, b)的公共正因子中最大的那个称为(a)和(b)的最大公约数,记作((a, b)),OI中一般记作(gcd(a, b))
如果观察质因子分解结果,容易发现(gcd(a, b))的每个指数都是(a, b)在对应位置上指数的最小值. 因此(a, b)的公共正因子集合正是(gcd(a, b))的因子集合.
同理,最小公倍数(mathrm{lcm}(a, b))是质因子分解中对应位置指数取最大值的结果. 观察到
这意味着
将(a, b)的公共正因子集合记为(CD(a, b)),则(gcd(a, b))是其中的最大元.
容易自行验证,将(a)加上若干倍的(b),则(a, b)的公共正因子集合不会改变,即
反之,对(b)加上若干倍的(a)也是一样.
考虑到(a mod b=a-lfloor frac{a}{b} floor b),我们得到(gcd(a, b)=gcd(a mod b, b)). 利用这一性质,求(gcd(a, b))时,只需不断地用较大的模较小的,过程中公共正因子集合不变,直至其中一个为零,另一个非零整数即为答案. 这一算法就是欧几里得算法,也被称为辗转相除法.
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
1.8 互质
如果两个整数的最大公因数是(1),则说它们互质. 考虑质因数分解,容易发现互质等价于质因子集合没有交集.
2 同余
2.1 定义
如果(m|(a-b)),则称(a)与(b)模(m)同余,记作(a equiv b pmod m)
因此可以发现,如果(n|m),那么(a equiv b pmod m Rightarrow a equiv b pmod n)
在模数为(m)时,依据同余关系可以把整数集分为(m)个模(m)剩余类,分别是模(m)得到(0, 1, dots, m-1)的整数组成的. 从每个剩余类中取一个元素,组成一个集合,就称为一个模(m)完全剩余系. 比较常用的模(m)完全剩余系是(0, 1, dots, m-1)
在模(m)意义下,容易验证加法、减法、乘法的结构是保持的,因此可以直接把这些运算搬到一个完全剩余系上. 但是除法却不太容易进行:例如模(6)时,(5 imes 2 equiv 2 imes 2 equiv 4 pmod 6),那么在模意义下用(4)除以(2)应该得到(2)还是(5)呢?
2.2 除法
在模(m)意义下做除法,就等价于给定(a, c),解同余方程(ax equiv c pmod m). 如果除法能够进行,就意味着这个同余方程有唯一解,而它又等价于下列不定方程:
这是由同余的定义得到的,下面我们对这种不定方程进行更深入的探讨.
2.3 Bezout定理
形如(ax+by=c)的不定方程有整数解的充要条件是(gcd(a, b)|c)
证明:
考虑(ax+by, x,y in mathbb{Z})可能取到的所有值,设其为(I_{a,b}),即
该不定方程有整数解当且仅当(c in I_{a, b}),因此我们希望弄清楚(I_{a,b})的结构. 容易验证(I_{a, b})对加减法、取模都是封闭的,下面我们取出(I_{a,b})中的最小正元素,设其为(d).
由于存在一组(x, y in mathbb{Z})使(d=ax+by),可以发现(gcd(a, b) | d Rightarrow gcd(a, b) leq d).
由于(a, b)都在(I_{a,b})中,考虑到取模的封闭性,(a mod d)和(b mod d)都在(I_{a,b})中,并位于([0, d))里. 但是考虑到(d)是最小正元素,它们不能取((0, d))中的值,因此(a mod d=b mod d=0),即(d|a, d|b). 这样一来,(d)是(a, b)的公约数,于是有(d leq gcd(a, b)). 综合上一段的结论,得到(d=gcd(a, b))
类似地,利用取模的封闭性,可以发现(I_{a,b})中的所有元素都是(d)的倍数. 到这里,(I_{a,b})的结构很清晰了——它恰好包含(mathrm{gcd}(a, b))的所有倍数:
于是证明了Bezout定理.
回到上一节的同余方程,Bezout定理意味着(ax equiv c pmod m)在(gcd(a, m) | c)时一定有解,而在(gcd(a, m) mid c)时一定无解.
2.4 扩展欧几里得算法
证明了解的存在性后,需要一个算法在解存在时找出(ax+by=c)的解.
在欧几里得算法的最后一步,即(a'=gcd(a, b), b'=0)时,解(a'x+b'y=c)是十分容易的:只需取(x=frac{c}{gcd(a, b)}, y=0). 下面在回溯过程中逐步倒推,得到最初的(ax+by=c)的解:
由(a, b)得到(b, a mod b)后,下一层递归并回溯得到了(bx'+(a mod b)y'=c)的一组解(x', y').
考虑到(a mod b = a- lfloor frac{a}{b} floor b),代入得到(bx'+(a- lfloor frac{a}{b} floor b)y'=c),将(a, b)的系数整理得到:
因此只需取(x=y', y=x'-lfloor frac{a}{b} floor y')即可
void exgcd(int a, int b, int c, int &x, int &y)
{
if(b == 0)
{
// assert: c % a == 0
x = c / a;
y = 0;
return;
}
int k = a / b;
exgcd(b, a - k * b, c, y, x);
y -= k * x;
}
2.5 (f(x)=ax mod m)的性质
想象一个钟面上顺时针刻着(0, 1, dots, m-1),从(0)出发,每一步向前前进(a)格,即从当前位置(c)前进到((c+a) mod m),那么哪些位置会被走到?
这其实对应着(f(x) = ax mod m),由于(x)取模(m)同余的值时函数值显然相同,我们只考虑(0 leq x < m).
在下面的讨论中,设(d=gcd(a, m))
根据2.3中的结论,我们知道(f(x))会且只会取到([0, m))中被(d)整除的那些值,共(frac{m}{d})个.
同时,考虑两个相差(frac{m}{d})的位置的函数值:
发现(f(x))有(frac{m}{d})的周期. 同时考虑到值域中的(frac{m}{d})个值都要被取到,它们在一个周期中都被取到了恰好一次.
于是(f(x))的结构就很清晰了:它以(frac{m}{d})为周期,每个周期中将(d)的(frac{m}{d})个倍数取遍一次. 在(x)取遍(0, 1, dots, m-1)时,它重复了(d)个周期.
2.6 乘法逆元
根据上一节的讨论,我们考虑一个特殊情形:在(a)与(m)互质时,(d=1).
这意味着当(x)取遍一次(mathbb{Z}_m)时,(ax)也取遍一次(mathbb{Z}_m). 这正好满足了做除法的要求:(x ightarrow ax)是一个(mathbb{Z}_m)到(mathbb{Z}_m)的双射,因此任给一个(c in mathbb{Z}_m),(ax equiv c pmod m)总有恰好一个解.
这也带来了一些其他的性质:由于(f)是单射,有(ax equiv ay pmod m Rightarrow x equiv y pmod m),这意味着可以从同余等式的两边消去同一个与模数互质的因子.
注意到(ax equiv 1 pmod m)也仅在(gcd(a, m)=1)时有解. 这个方程的解被称为(a)的乘法逆元,记作(a^{-1}),因为它的表现就像实数乘法中的(frac{1}{a})一样.
模意义下的乘法保持了交换律和结合律——这允许我们十分方便地使用乘法逆元:要解(ax equiv c pmod m),只需两边左乘(a^{-1})(实际上由于交换律,并不需要说明是左乘),得到
再利用结合律,并考虑到(a^{-1}a equiv 1 pmod m)
类似地,将(ax equiv ay pmod m)两边乘(a^{-1}),便可直接得到(x equiv y pmod m)
待续:乘法的结构(原根,欧拉定理,离散对数),(mathbb{Z}_n)间笛卡尔积的结构(中国剩余定理),Dirichlet卷积、Mobius反演、杜教筛