基础数论

1 同余

1.1 定义

a,b 为两个整数,且 ab 能被自然数 m 整除,就说 ab 关于模 m 同余,记作 ab(modm)

1.2 性质

同余有以下性质:

  1. 自反性: aa(modm)
  2. 对称性:若 ab(modm),则 ba(modm)
  3. 传递性:若 ab(modm)bc(modm),则 ac(modm)
  4. 同加性:若 ab(modm),则 a+cb+c(modm)
  5. 同乘性:若 ab(modm),则 acbc(modm)
  6. 同幂性:若 ab(modm),则 acbc(modm)

注意同余没有同除性,但是有消去律。

2 素数

2.1 素数的定义

一个大于 1 的自然数,除了 1 和他自身外,不能被其他自然数整除的数叫做素数

2.2 有关素数的定理

2.2.1 算数基本定理

任何一个大于 1 得正整数都能被唯一分解为有限个素数的乘积。即:

N=p1c1p2c2p3c3pmcm

其中,ci 均为正整数,pi 均为素数。

2.2.2 N 与其因子的大小关系

N 中至多只有一个大于 N 的因子。

2.2.3 分解质因数

采用试除法,从 2n 枚举。最坏时间复杂度 O(n)

2.3 素数判定

仍然考虑试除法,复杂度 O(n)

2.4 筛素数

2.4.1 埃氏筛

对于求出某个范围内的所有素数,我们从 2 开始,将 2 的倍数去掉;然后是 3, 将 3 的倍数去掉;……以此类推。

这样做的时间复杂度为 O(nloglogn),原因在于一个合数会被多次筛去,浪费了时间。

2.4.2 线性筛(欧拉筛)

为了解决埃氏筛的弊端,每个数现在只会被它的最小质因子筛掉。

流程如下:

  1. 从小到大枚举每个数

  2. 如果当前数没有被筛掉,则必定是素数,记录。

  3. 枚举已记录的素数

    1). 如果合数没有越界,划掉合数

    2). 如果 imodp=0,表明 i 有了一个最小质因子 p。根据欧拉筛的定义,到此退出即可。

代码:

int prim[Maxn], tot;
bool vis[Maxn];

void prime() {
	for(int i = 2; i <= Maxn; i++) {
		if(!vis[i]) prim[++tot] = i;
		for(int j = 1; i * prim[j] <= Maxn; j++) {
			vis[i * prim[j]] = 1;
			if(i % prim[j] == 0) break; 
		}
	}
}

2.5 欧拉函数

2.5.1 定义

  • 对于正整数 n,他的欧拉函数 φ(n) 指的是小于等于 n 的数中与 n 互质的数的个数。

  • 通项公式为:

φ(n)=n×i=1k(11pi)

​ 其中,pin 的所有质因数。

2.5.2 性质

  1. φ(1)=1
  2. p 为素数,则 φ(p)=p1
  3. p 为素数,则 φ(pk)=(p1)×pk1
  4. 欧拉函数是积性函数

2.5.3 求解欧拉函数

2.5.3.1 试除法

如果只要求一个数的欧拉函数,直接根据定义试除法即可。

int get_phi(int n) {
	int ans = n;
	int m = sqrt(n);
	for(int i = 2; i <= m; i++) {
		if(n % i == 0) {
			ans = ans * (i - 1) / i;
		}
		while(n % i == 0) n /= i;
	}
	if(n > 1) ans = ans * (n - 1) / n;
	return ans;
}

2.5.3.2 筛法求欧拉函数

上面算法的时间复杂度为 O(n),如果要求 1n 所有数的欧拉函数,总复杂度是 O(nn),不够优。

我们考虑在筛法的过程中求出欧拉函数。

phii 表示 φ(i)

若当前 i 为素数,则 phii=i1

i 不为素数:

m 的最小质因子为 pj,则 m 应该被 i×pj 筛去。

1). 若 i 能被 pj 整除,则 i 包含了 m 所有的质因子。

​ 则 phim=m×i=1kpi1pi=pj×i×i=1kpi1pi=pj×phii

2). 若 i 不被 pj 整除,则 ipj 互质。

​ 则 phim=phipj×i=phipj×phii=(pj1)×phii

代码:

bool vis[Maxn];
int prim[Maxn], phi[Maxn], cnt;

void get_phi() {
	phi[1] = 1;
	for(int i = 2; i <= Maxn; i++) {
		if(!vis[i]) {
			prim[++cnt] = i;
			phi[i] = i - 1;
		}
		for(int j = 1; i * prim[j] <= Maxn; j++) {
			vis[i * prim[j]] = 1;
			phi[i * prim[j]] = (prim[j] - 1) * phi[i];
			if(i % prim[j] == 0) {
				phi[i * prim[j]] = prim[j] * phi[i];
				break;
			}
		}
	}
}

2.6 求约数个数和约数和

2.6.1 约数个数

2.6.1.1 公式法

根据算数基本定理,设:

n=p1c1p2c2p3c3pmcm

n 的约数个数 d(n)=(c1+1)(c2+1)(cm+1)

由乘法原理易证。

2.6.1.2 筛法求约数个数

dii 的约数个数,numii 的最小质因数个数。

i 为素数,则 di=2,numi=1

i 为合数:

m 的最小质因子为 pj,则 m 应该被 i×pj 筛去。

1). 当 ipj 整除时

​ 设 i=p1c1p2c2p3c3pmcm

​ 则 dm=di×pj=(1+c1+1)(1+c2)(1+cm)

​ 即 dm=di÷(numi+1)×(numi+2)

​ 同时,numm=numi+1

2). 当 i 不被 pj 整除时

​ 设 i=p1c1p2c2p3c3pmcm

​ 则 dm=dpj×i=(1+c1)(1+c2)(1+cm)(1+1)=2di

​ 同时 numm=1

代码跟筛法求欧拉函数差不多,全部带入公式计算即可。

2.6.2 约数和

2.6.2.1 公式法

根据算数基本定理,设:

n=p1c1p2c2p3c3pmcm

n 的约数个数为 (p10+p11+p12+p1r1)(p20+p21+p22+p2r2)(pm0+pm1+pm2+pmrm)

2.6.2.2 筛法求约数之和

di 表示 i 的约数之和,numi 表示是最小值因子所在的项,即 (p10+p11+p12+p1r1)

i 为素数,则 di=numi=i+1

i 为合数:

m 的最小质因子为 pj,则 m 应该被 i×pj 筛去。

1). 当 ipj 整除时

​ 设 i=p1c1p2c2p3c3pmcm

​ 则 dm=di×pj=(p10+p11+p12+p1r1+1)(p20+p21+p22+p2r2)(pm0+pm1+pm2+pmrm)

​ 即 dm=di÷numi×(numi+pjrj+1)=di÷numi×(numi×pj+1)

​ 同时,numm=numi×pj+1

2). 当 i 不被 pj 整除时

​ 设 i=p1c1p2c2p3c3pmcm

​ 则 dm=dpj×i=(p10+p11+p12+p1r1+1)(p20+p21+p22+p2r2)(pm0+pm1+pm2+pmrm)(pj0+pj1)=di(1+pj)

​ 同时 numm=pj+1

代码与筛法求约数个数一样,就不放了。

2.7 欧拉定理与费马小定理

2.7.1 欧拉定理

gcd(a,m)=1,则:

aφ(m)1(modm)

特别的,当 m 为质数时,根据欧拉函数的定义代入,即可得到费马小定理。

2.7.2 费马小定理

m 为素数,则:

am11(modm)

2.7.3 扩展欧拉定理

ab{abmodφ(p),gcd(a,p)=1ab,gcd(a,p)1,b<φ(p)(modp)abmodφ(p)+φ(p),gcd(a,p)1,bφ(p)

对于 b 较大的情况,可以用扩展欧拉定理将 b 降下去,然后进行快速幂。

2.7.3.1 秦九昭算法

对于多项式 f(x)=anxn+an1xn1+an2xn2+a1x+a0,一般人工计算需要进行 n(n+1)2 次乘法,n 次加法。

对于 f(x) 进行变形:

f(x)=anxn+an1xn1+an2xn2+a1x+a0=(anxn1+an1xn2+an2xn3+a1)x+a0==(((anx+an1)x+an2)x+a1)x

这样就只需要做 n 次乘法,n 次加法即可

扩展欧拉定理模板:

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int Maxn = 2e5 + 5;

int a, p, b;
string s;

int get_phi(int k) {
	int ans = k, m = sqrt(k);
	for(int i = 2; i <= m; i++) {
		if(k % i == 0) {
			ans = ans * (i - 1) / i;
		}
		while(k % i == 0) k /= i;
	}
	if(k > 1) ans = ans * (k - 1) / k;
	return ans;
}

int down_pow(int phi) {
	int res = 0;
	bool flag;
	for(int i = 0; i < s.size(); i++) {
		res = res * 10 + s[i] - 48;
		if(res >= phi) {
			res %= phi;
			flag = 1;
		}
	}
	if(flag) res += phi;
	return res;
}

int qpow(int a, int b, int p) {
	int res = 1;
	while(b) {
		if(b & 1) {
			res *= a;
			res %= p;
		}
		a *= a;
		a %= p;
		b >>= 1;
	}
	return res;
}

signed main() {
	ios::sync_with_stdio(0);
	cin >> a >> p >> s;
	int _phi = get_phi(p);
	b = down_pow(_phi);
	cout << qpow(a, b, p);
	return 0;
}

其中函数 down_pow 部分就利用到了秦九昭算法来对 b 降幂。

3 最大公约数

3.1 欧几里得算法

欧几里得算法是常用的求 gcd 的算法之一。

他运用了一个性质:gcd(a,b)=gcd(b,amodb)

代码:

void gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}

3.2 裴蜀定理

定理说明,设 a,b 是不全为 0 的整数,则存在 x,y,使得

ax+by=gcd(a,b)

3.2.1 裴蜀定理的简单推广

a1,a2,an 是不全为 0 的整数,则存在 x1,x2,,xn,使得:

i=1naixi=gcd(a1,a2,,an)

3.3 扩展欧几里得算法(扩欧)

扩欧算法常用于求方程 ax+by=gcd(a,b) 的整数解(特解)。

推导:

ax1+by1=gcd(a,b)

bx2+(b%a)y2=gcd(b,a%b)

gcd(a,b)=gcd(b,a%b)

ax1+by1=bx2+(b%a)y2

a%b=aab×b

ax1+by1=bx2+(aab×b)y2=ay2+b(x2aby2)

因此,我们可以层层向下递归求解,然后回带到上一层,

代码:

int exgcd(int a, int b, int &x, int &y) {
	if(b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	int ret = exgcd(b, a % b, x, y);
	int t = x;
	x = y;
	y = t - a / b * y;
	return ret;
}

3.3.1 扩欧后构造通解

通解公式为:

{x=x0+bgcd(a,b)ky=y0agcd(a,b)k

3.3.2 扩欧求解线性同余方程

问题:求方程 axb(modp) 的一个整数解。

将同余方程转化为不定方程:

axb(modp)ax=p(y)+b,即 ax+py=b

由裴蜀定理知,当 bgcd(a,p) 时有解。

接下来由扩欧求出方程 ax+py=gcd(a,p) 的特解,最后把得到的 x 乘上 bgcd(a,p) 就是原方程特解。

3.3.2.1 同余方程的消去律

虽然同余没有同除性,但同余方程有消去律。

axbx(modp),则 ab(modpgcd(x,p))

4 逆元

4.1 定义与用途

ax1(modp),称 xa 在模 p 意义下的乘法逆元,记作 a1

仅当 gcd(a,p)=1 时存在乘法逆元。

上面曾经讲到,同余不具有同除性,这是不能忍受的。于是我们就可以用逆元来解决问题。

根据定义推到,得出的结论是:abmodp=a×(b1)modp,于是就转化为了乘法问题。

4.2 求乘法逆元

4.2.1 费马小定理

条件:当 p 为素数时,可以用费马小定理求解。

根据费马小定理 ap11(modp),可得 a×ap2(modp)

所以此时的乘法逆元为 ap2,用快速幂求解即可。

4.2.2 线性求逆元

1p1 中每个数关于模 p 的逆元(p 为素数)。

推导:

首先,111(modp)

接下来,设 p=ki+r,其中 1<i<p,r<i

在模 p 意义下,得到 ki+r0(modp)

两边同乘 i1×r1,则:

k×i×i1×r1+r×i1×r10(modp)

k×r1+i10(modp)

i1k×r1(modp)

i1pi×r1(modp)

i1pi×(pmodi)1(modp)

i1invi,则:

invi=(pi×invpmodi)modp
i1invi,则:

invi=(pi×invpmodi)modp

若要求正整数,则递推公式为 :

invi=((ppi)×invpmodi)modp

代码:

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

4.2.3 欧拉定理求逆元

回顾欧拉定理:当 gcd(a,n)=1 时,aφ(p)1(modp)。所以此时 aφ(p)1 就是 a 关于 n 的逆元。

4.2.4 扩欧求逆元

ax1(modb),理应有一种熟悉的感觉。他就是一个同余方程,所以可以扩欧。要求 gcd(a,b)=1

posted @   UKE_Automation  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示