📕NOIP 数论笔记,素数筛法、欧几里得、扩展欧几里得、欧拉函数、欧拉定理、费马小定理、逆元、求解同余方程(中国剩余定理)


素数筛法

对于少量的判断一个 n 是不是素数,我们可以通过暴力 O(n)O(\sqrt{n}) 的复杂度解决,而当题目需要判断大量素数时便引入了筛法求素数。

朴素的筛法

遇到一个数把它的所有倍数标记掉,依次筛,没有标记的就是素数。复杂度 O(nlogn)O(n\log n)

1
2
3
4
5
6
7
8
9
void init(int n) {
int up = (int)sqrt(n) + 1;
for (int i = 2; i <= up; i++)
for (int j = i * i; j <= n; j += i)
flag[j] = true;
for (int i = 2; i <= n; i++) {
if (!flag[i]) p[tot++] = i;
}
}

线性筛素数

O(n)O(n) 筛素数,每一个数只被其最小的素因子筛选到

1
2
3
4
5
6
7
8
9
10
11
12
void init(int n) {
for (int i = 2; i <= n; i++) {
if (!flag[i]) p[tot++] = i;
for (int j = 0; j < tot; j++) {
if (i * p[j] > n) break;
flag[i * p[j]] = true; //p[j]是i * p[j]的最小素因子
if (i % p[j] == 0) { // 再往后p[j]就不是i * p[j]的最小素因子了
break;
}
}
}
}

欧几里得

求两数最大公约数

辗转相减

求两数 a,ba,b 的最大公约数 gcd(a,b)\gcd(a,b),假设 a>ba>b ,则 gcd(a,b)=gcd(ab,b)\gcd(a,b)=gcd(a-b,b) ,直到其中一个为零,另一个就是 gcd\gcd

证明:

  • a=k1×ga=k_1\times g,令 b=k2×gb=k_2\times g
  • 假设 a,ba,b 的最大公约数是 gggcd(k1,k2)=1\gcd(k_1,k_2)=1
  • 每次操作令 a=aba=a-ba=(k1k2)×ga=(k_1-k_2)\times gb=k2×gb=k_2 \times g
  • 可以证明 k1k2k_1-k_2k2k_2 还是互质的(反证法),那么当其中一个 kk 减到零,另一个 kk 为一便是 gg 了。
1
2
3
4
5
int gcd(int x, int y) {
if(x < y) return gcd(y, x);
if(y == 0) return x;
return gcd(x - y, y);
}

辗转相除

显然上面的做法复杂度太高,有许多多余的计算,当 a=101,b=2a=101,b=2 时就要减好几次,显然的目的是减到 a<ba<b 为止再交换 aabb,那么就等同于 amodba\bmod b,故 gcd(a,b)=gcd(amodb,b)\gcd(a,b)=\gcd(a\bmod b,b)

1
2
3
int gcd(int a, int b) {
return !b ? a : gcd(b, a % b);
}

扩展欧几里得

通过扩展欧几里得解决形如 ax+by=cax+by=c 的二元一次方程,求解的存在性,特解、通解。

解的存在性

有一个显然的定理:gcd(k1,k2)=1\gcd(k_1,k_2)=1 则方程 k1x+k2y=1k_1x+k_2y=1 一定有解(上面的辗转相减告诉我们对于互质的两数 k1,k2k_1,k_2,辗转相减可以减到 11。对于上式,乘上 gcd(a,b)\gcd(a,b):

k1gcd(a,b)x+k2gcd(a,b)y=gcd(a,b)k_1\gcd(a,b)x+k_2\gcd(a,b)y=\gcd(a,b)

ax+by=gcd(a,b)ax+by=\gcd(a,b)

一定有解。

那么若 ccgcd(a,b)\gcd(a,b) 的倍数,即 cmodgcd(a,b)=0c\bmod \gcd(a,b)=0 则有解。

特解与通解

对于原方程 ax+by=cax+by=c,我们其实只要求出

ax+by=gcd(a,b)ax+by=\gcd(a,b)

的解再乘上 cgcd(a,b)\dfrac{c}{gcd(a,b)} 就行了。我们考虑缩小规模,假设我们已经求出

(ab)x1+by1=gcd(ab,b)(a-b)x_1+by_1=gcd(a-b,b)

的解 x1,y1x_1,y_1,那么变化一下式子可得

ax1bx1+by1=gcd(a,b)ax_1-bx_1+by_1=\gcd(a,b)

ax1+b(y1x1)=gcd(a,b)ax_1+b(y_1-x_1)=\gcd(a,b)

观察一下式子,是不是 x=x1,y=y1x1x=x_1,y=y_1-x_1
所以我们可以把原问题变成一个规模更小的子问题,求出子问题的解,推出当前的解,故递归求解。

考虑到辗转相减可以用辗转相除替代

bx1+amodby1=gcd(b,amodb)bx_1+a\bmod by_1=\gcd(b,a\bmod b)

由("/"是取整除)

amodb=aa/b×ba\bmod b=a-a/b\times b

代入得

bx1+(aa/b×b)y1=gcd(b,amodb)bx_1+(a-a/b\times b)y_1=gcd(b,a\bmod b)

等价于

ay1+b(x1a/b×y1)=gcd(a,b)ay_1+b(x_1-a/b\times y_1)=gcd(a,b)

x=y1,y=x1a/b×y1x=y_1,y=x_1-a/b\times y_1

不断缩小规模最后 a=gcd(a,b),b=0a=\gcd(a,b),b=0 ,得方程 gcd(a,b)×x+0×y=gcd(a,b)\gcd(a,b)\times x+0\times y=\gcd(a,b),得到一组特解 x=1,y=0x=1,y=0,将这个方程反推回去可得原方程解。

1
2
3
4
5
6
7
8
9
10
int exgcd(int a, int b, int &x, int &y) {
if(!b) {
x = 1, y = 0;
return a;
}else {
int r = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return r;
}
}

函数执行完毕后代码里面的 x,yx,y 就是方程 ax+by=gcd(a,b)ax+by=\gcd(a,b) 的一组特解,通解的变化规律就是 xxyy 的值往相反的方向变化,比如 xx 变大一些 yy 变小一些,为使得答案不变,设这个变化的最小单位值为 d1,d2d_1,d_2

a(x+d1)+b(yd2)=gcd(a,b)a(x+d_1)+b(y-d_2)=\gcd(a,b)

a×d1=b×d2a\times d_1=b\times d_2

同除 gcd(a,b)\gcd(a,b),令 k1=a/gcd(a,b),k2=b/gcd(a,b)k_1=a/\gcd(a,b),k_2=b/\gcd(a,b)

k1×d1=k2×d2k_1\times d_1 = k_2\times d_2

k1,k2k_1,k_2 互质,显然 d1=k2,d2=k1d_1=k_2,d_2=k_1

故通解为

(x+k×d1,yk×d2)(x+k\times d_1,y-k\times d_2)

(x+k×b/gcd(a,b),yk×a/gcd(a,b))(x+k\times b/\gcd(a,b),y-k\times a/\gcd(a,b))

欧拉函数

定义

欧拉函数 φ(n)\varphi(n) 表示小于或者等于 nn 的正整数中与 nn 互质的数的数目, 例如 φ(8)=4\varphi(8) = 4, 因为 1,3,5,71,3,5,788 互质。

欧拉函数值

特殊的 φ(1)=1\varphi(1)=1, φ(p)=1\varphi(p)=1,(pp 是素数);若 nn 是素数 ppkk 次幂

φ(n)=φ(pk)=pkpk1=(p1)pk1\varphi(n)=\varphi(p^k)=p^k-p^{k-1}=(p-1)\cdot p^{k-1}

相当于总数减去 p 的倍数。利用类似的想法我们考虑求 φ(p1k1p2k2)\varphi(p_1^{k_1}\cdot p_2^{k_2}),通过简单容斥我们知道,它应该等于总数减去 p1p_1 的倍数 p2p_2 的倍数,再加上 p1,p2p_1,p_2 的倍数

φ(p1k1p2k2)=p1k1p2k2p1k11p2k2p1k1p2k21+p1k11p2k21=(p1k1p1k11)(p2k2p2k21)\begin{aligned} \varphi(p_1^{k_1}\cdot p_2^{k_2})&=p_1^{k_1}\cdot p_2^{k_2}-p_1^{k_1-1}\cdot p_2^{k_2}-p_1^{k_1}\cdot p_2^{k_2-1}+p_1^{k_1-1}\cdot p_2^{k_2-1}\\ &=\left(p_1^{k_1}-p_1^{k_1-1}\right)\left(p_2^{k_2}-p_2^{k_2-1}\right) \end{aligned}

而我们发现

φ(p1k1)φ(p2k2)=(p1k1p1k11)(p2k2p2k21)\varphi\left(p_1^{k_1}\right)\cdot \varphi\left(p_2^{k_2}\right) = \left(p_1^{k_1}-p_1^{k_1-1}\right)\left(p_2^{k_2}-p_2^{k_2-1}\right)

φ(p1k1p2k2)=φ(p1k1)φ(p2k2)\varphi\left(p_1^{k_1}\cdot p_2^{k_2}\right)=\varphi\left(p_1^{k_1}\right)\cdot \varphi\left(p_2^{k_2}\right),这体现了欧拉函数是积性函数的性质,

积性函数: 对于两个互质的 n,mn,m,若 f(nm)=f(n)f(m)f(n\cdot m)=f(n)\cdot f(m),称 ff 为积性函数

我们将 nn 因式分解

n=p1k1p2k2prkrn=p_1^{k_1}p_2^{k_2}\dotsb p_r^{k_r}

φ(n)=i=1rpiki1(pi1)=ni=1r(pi1pi)\varphi(n)=\prod_{i=1}^r p_i^{k_i-1}(p_i-1)=n\cdot \prod_{i=1}^r (\frac{p_i-1}{p_i})

欧拉函数的性质

  1. 欧拉函数是积性函数,当正整数 m,nm,n 互质时,φ(mn)=φ(m)φ(n)\varphi(m\cdot n) = \varphi(m)\cdot \varphi(n)
  2. nn是奇数时,φ(2n)=φ(n)\varphi(2\cdot n) = \varphi(n)
  3. φ(n)=φ(n/p)p\varphi(n) = \varphi(n/p)\cdot p, (pp 能整除 n/pn/p),
  4. φ(n)=φ(n/p)(p1)\varphi(n) = \varphi(n/p)\cdot (p−1), (ppn/pn/p 互质,且 pp 是质数)
  5. dnφ(d)=n\sum_{d|n} \varphi(d)=n (nn等于它所有因子的欧拉函数之和)

性质 2 证明:φ(2)=1\varphi(2)=1 ,再利用积性函数
性质 3 证明:可以利用上一小节最后一个式子来推
性质 4 证明:利用积性函数性质和φ(p)=p1\varphi(p)=p-1
性质 5 证明:设 f(n)=dnφ(d)f(n)=\sum_{d|n}\varphi(d) ,令 n,mn,m 互质

f(nm)=dnmφ(d)=d1nφ(d1)d2mφ(d2)=f(n)f(m)f(nm)=\sum_{d|nm}\varphi(d)=\sum_{d_1|n}\varphi(d_1)\cdot \sum_{d_2|m}\varphi(d_2)=f(n)\cdot f(m)

(因为 n,mn,m 互质,那么它们的因子也相互质,故 nmnm 的因子可以看出 nn 的因子乘上 mm 的因子)
所有 f(n)f(n) 是积性函数

f(pc)=dpcφ(d)=φ(1)+φ(p)+φ(p2)++φ(pc)=pcf(p^c)=\sum_{d|p^c} \varphi(d)=\varphi(1)+\varphi(p)+\varphi(p^2)+\dotsc +\varphi(p^c)=p^c

因此

f(n)=i=1rf(pici)=i=1rpici=nf(n)=\prod_{i=1}^r f(p_i^{c_i})=\prod_{i=1}^r p_i^{c_i}=n

代码实现

法一:利用定义 φ(n)=ni=1r(pi1pi)\displaystyle\varphi(n)=n\cdot \prod_{i=1}^r (\frac{p_i-1}{p_i}) ,通过普通筛求

1
2
3
4
5
6
7
8
9
10
void init(int n) {
varphi[1] = 1;
for (int i = 2; i <= n; i++)
varphi[i] = i;
for (int i = 2; i <= n; i++) {
if (varphi[i] = i) //素数,没有被筛到故phi没变过
for (int j = i; j <= n; j += i)
varphi[j] = varphi[j] / i * (i - 1); //先除再乘防溢出
}
}

法二:线性筛递推,利用性质 3 性质 4 : φ(n)=φ(n/p)p\varphi(n) = \varphi(n/p)\cdot p, (pp 能整除 n/pn/p) ; φ(n)=φ(n/p)(p1)\varphi(n) = \varphi(n/p)\cdot (p−1), ( ppn/pn/p 互质,且 pp 是质数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void init(int n) {
for (int i = 2; i <= n; i++) {
if (!flag[i]) {
p[tot++] = i;
varphi[i] = i - 1; //是素数\varphi(p)=p-1
}
for (int j = 0; j < tot; j++) {
if (i * p[j] > n) break;
flag[i * p[j]] = 1;
if (i % p[j] == 0) {
varphi[i * p[j]] = varphi[i] * p[j]; //性质3
break;
}
varphi[i * p[j]] = varphi[i] * (p[j] - 1); //性质4
}
}
}

法三:利用性质 5 dnφ(d)=n\sum_{d|n} \varphi(d)=n

1
2
3
4
5
6
7
void init(int n) {
for (int i = 1; i <= n; i++) varphi[i] = i;
for (int i = 1; i <= n; i++) {
for (int j = i + i; j <= n; j += i)
varphi[j] -= varphi[i];
}
}

欧拉定理

欧拉定理的定义是:如果 a,na,n 为正整数,且 gcd(a,n)=1\gcd(a,n)=1 则有:

aφ(n)1(modn)a^{\varphi(n)}\equiv 1 \pmod{n}

证明

令集合

S={x1,x2,,xφ(n)}S=\left\{ x_1,x_2,\dots,x_{\varphi(n)}\right\}

表示所有的小于 nn 并且与 nn 互质的数;再令集合

T={ax1modn,ax2modn,,axφ(n)modn}T=\left\{a\cdot x_1\bmod n,a\cdot x_2\bmod n,\dots,a\cdot x_{\varphi(n)}\bmod n\right\}

由于 gcd(a,n)=1,gcd(xi,n)=1\gcd(a,n) = 1,\gcd(x_i,n) = 1, 所以 gcd(axi,n)=1\gcd(a\cdot x_i,n) = 1,所以

gcd(aximodn,n)=1\gcd(a\cdot x_i\bmod n,n) = 1

容易通过反证得出 TT 中的元素是两两不相同的,因此集合 SSTT 是相同的,所以我们构造出

  aφ(n)x1x2xφ(n)(modn)  ax1ax2axφ(n)(modn)  x1x2xφ(n)(modn)\begin{aligned} &\;a^{\varphi(n)}\cdot x_1\cdot x_2\cdot\dotso\cdot x_{\varphi(n)}& \pmod{n} \\ \equiv& \;a\cdot x_1 \cdot a \cdot x_2 \cdot \dotso \cdot a \cdot x_{\varphi(n)} &\pmod{n} \\ \equiv& \;x_1 \cdot x_2 \cdot \dotso \cdot x_{\varphi(n)} &\pmod{n} \end{aligned}

所有 aφ(n)1(modn)a^{\varphi(n)}\equiv 1 \pmod{n},欧拉定理得证。

扩展欧拉定理

ab{abmodφ(p),gcd(a,p)=1ab,gcd(a,p)1,b<φ(p)abmodφ(p)+φ(p),gcd(a,p)1,bφ(p)(modp)a^b\equiv \begin{cases} a^{b\bmod\varphi(p)},\,&\gcd(a,\,p)=1\\ a^b,&\gcd(a,\,p)\ne1,\,b<\varphi(p)\\ a^{b\bmod\varphi(p)+\varphi(p)},&\gcd(a,\,p)\ne1,\,b\ge\varphi(p) \end{cases} \pmod p

一个应用

求最小的 kk 满足 ak1(modn)a^k \equiv 1 \pmod{n}kk 一定是 φ(n)\varphi(n) 的因子

费马小定理

费马小定理是欧拉定理的特殊情况,即当 pp 为素数时

ap11(modp)a^{p-1}\equiv 1\pmod{p}

乘法逆元

当我们要计算 abmodc\dfrac{a}{b} \bmod c 时,又因为 a,ba,b 很大,不能用高精保存时,典型的例子就是高精取模。这个时候我们可以用到乘法逆元,将除法转换成乘法并提前取模。

解不定方程

设:

invb1(modc)inv\cdot b\equiv 1 \pmod{c}

我们称 invinv (也可以记 b1(modc)b^{-1}\pmod{c})为 bbcc 的逆元,就可以将除法转化为乘法,即 abmodc=(ainv)modc\dfrac{a}{b}\bmod c=(a\cdot inv)\bmod c

那是否一定存在逆元呢?我们考虑下式

invb1(modc)inv\cdot b\equiv 1 \pmod{c}

等价于

invb+kc=1inv\cdot b+k\cdot c=1

b,cb,c 是已知的,本质上是求上述的二元一次方程的一个正整数解,那么用扩展欧几里得就可以解决,若 gcd(b,c)=1\gcd(b,c)=1 则有解,即存在逆元。

1
2
3
4
5
6
int inv(int b, int c) {
int x, y;
exgcd(b, c, x, y);
x = (x % c + c) % c; //避免负数
return x;
}

欧拉定理

特殊的,当 cc 是质数时,可利用费马小定理

bc11(modc)b^{c-1}\equiv 1\pmod{c}

同乘 invinv

bc2inv(modc)b^{c-2}\equiv inv \pmod{c}

所以 inv=bc2modcinv=b^{c-2}\bmod c,用快速幂可以解决

稍微一般的,若 cc 不是质数,可用欧拉定理

bφ(c)1(modc)b^{\varphi(c)}\equiv 1 \pmod{c}

易得 inv=bφ(c)1modcinv=b^{\varphi(c)-1}\bmod c

线性求逆元

可以在 O(n)O(n) 的时间内求出 modp\bmod p 意义下(pp 为质数),[1,n][1,n] 所有数的逆元。

p=id+rp=i\cdot d+r,其中 d=pi,  r=pmodid=\lfloor\frac{p}{i}\rfloor,\;r=p\bmod i

那么有

id+r0(modp)i\cdot d + r \equiv 0 \pmod{p}

两边同乘 i1r1i^{-1}r^{-1}

r1d+i10(modp)r^{-1}\cdot d + i^{-1} \equiv 0 \pmod{p}

得到

i1dr1(modp)i^{-1} \equiv -d \cdot r^{-1} \pmod{p}

由于 r<ir<i ,就可以从前往后推了。

1
2
inv[0] = inv[1] = 1;
for (int i = 2; i <= n; ++i) inv[i] = (p - p / i) * inv[p % i] % p;

线性求任意 n 个数逆元

上面的方法只能求 [1,n][1,n] 的逆元,若求任意 nn 个数 ai<pa_i < p,可以这样。

记前缀积为 sis_i,用快速幂求出 sns_n 的逆元 sn1s_n^{-1},根据 si1=si+11ai+1s_{i}^{-1}=s_{i+1}^{-1}\cdot a_{i+1} 可以得到任意 sis_i 的逆元。

然后 ai1=si1si1a_{i}^{-1}=s_{i}^{-1}\cdot s_{i-1} 即得所求。

复杂度 O(n+logp)O(n+\log p)

中国剩余定理

求解同余方程组。

给你如下同余方程组,求解 xx

{xa1(modm1)xa2(modm2)xan(modmn)\left\{\begin{aligned} x &\equiv a_1 \pmod{m_1}\\ x &\equiv a_2 \pmod{m_2}\\ &\vdots\\ x &\equiv a_n \pmod{m_n} \end{aligned}\right.

其中 m1,m2,,mnm_1,m_2,\dotsc,m_n 两两互质,我们设

M=i=1nmiM=\prod_{i=1}^n m_i

Mi=M/miM_i=M/m_i

sis_i 是同余方程

Mi×si1(modmi)M_i\times s_i\equiv 1\pmod{m_i}

的一个解,则同余方程组的解为

x=i=1naiMisix=\sum_{i=1}^n a_i\cdot M_i\cdot s_i

证明

由于 ij,gcd(mi,mj)=1i\ne j,\gcd(m_i,m_j)=1,则 gcd(Mi,mi)=1\gcd(M_i,m_i)=1,故必然存在 sis_iMiM_imim_i 的逆元。

考察乘积 aisiMia_is_iM_i 可知

aisiMiai1ai(modmi)a_is_iM_i\equiv a_i\cdot 1\equiv a_i \pmod{m_i}

ji:aisiMi0(modmj)\forall j\ne i:a_is_iM_i\equiv 0 \pmod{m_j}

所以解 x=a1s1M1+a2s2M2++ansnMnx=a_1s_1M_1+a_2s_2M_2+\dotsc+a_ns_nM_n满足

x=aisiMi+jiajsjMjai+ji0ai(modmi)x=a_is_iM_i+\sum_{j\ne i} a_js_jM_j\equiv a_i+\sum_{j\ne i} 0 \equiv a_i \pmod{m_i}

说明 xx 就是方程的一个解。另外特解可以表示为

x+kMx+k\cdot M

具体做法就是做 nnexgcd\operatorname{exgcd} 求出 sis_i,再求出 xx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int crt(int n, int a[], int m[]) {
int mul = 1;
for (int i = 0; i < n; i++) {
mul = mul * m[i];
}
int ret = 0;
for (int i = 0; i < n; i++) {
int x, y;
exgcd(mul / m[i], m[i], x, y);
x = (x % m[i] + m[i]) % m[i];
ret += mul / m[i] * x * a[i];
ret %= mul;
}
return ret;
}

END

Update 2021-3-27 : 修复了一些 bug,使公式表达更规范
Update 2021-7-17 : fix some bug, add something in 乘法逆元