数学知识(一)

温馨提示:本篇含有大量 LATEX 公式,可能加载较慢!

一. 质数

定义

若一个正整数的因数只有 1 和它本身,那么这个数就是质数,否则为合数。特殊的,1 既不是质数,又不是合数。

质数分布定理

limnπ(n)=nlnn

用人话来说就是当 n 趋近正无穷时,不超过 n 的质数大约有 nlnn 个。

质数的判定

如何判定一个正整数 n 是不是质数?

试除法

从定义出发,枚举 2n 的整数,一个一个试能不能整除。若能则为合数,否则为质数。

Code:

bool check(int n) {
    if(n < 2) return false;
    for(int i = 2; i <= n / i; i++)
        if(n % i == 0) return false;
    return true;
}

分解质因数

算数基本定理

内容:

任何一个大于 1 正整数都能唯一分解成有限个质数的乘积,可写作:

x=p1α1×p2α2××pkαn

在分解质因数时也可以用试除法,枚举 2n 的整数,若找到了一个能整除的数,就把它从 n 中除尽,这样就能保证后面满足这个条件的数一定是质数,就不用额外判断是不是质数了。

Code:

for(int i = 2; i <= n / i; i++) {
    int s = 0;
    while(n % i == 0) n /= i, s++; //除尽
    if(s > 0) printf("%d %d\n", i, s);
}
if(n > 1) printf("%d %d\n", n, 1); //可能会剩下一个比较大的质因子

质数筛

1. 埃氏筛

思路:

把找到的质数的所有不大于 n 的倍数全都筛掉,剩下的就全是质数了。
但是,同一个数会被它的不同质因子都筛一遍,效率有所降低。

时间复杂度:O(nloglogn)

代码:

void ai(int n) {
	for(int i = 2; i <= n; i++) {
		if(!st[i]) {
			primes[++tt] = i;
			for(int j = 2; i * j <= n; i++) st[i * j] = true;
		}
	}
}

2. 欧拉筛(线性筛)

思路:

在埃氏筛的基础上进行了优化,保证每个合数只会被它的最小质因子筛掉,时间复杂度有所降低。

时间复杂度:O(n)

代码:

void ol(int n) {
	for(int i = 2; i <= n; i++) {
		if(!st[i]) primes[++tt] = i;
		for(int j = 1; j <= tt && i * primes[j] <= n; j++) {
			st[i * primes[j]] = true; 
			if(i % primes[j] == 0) break; //确保primes[j]一定是i的最小质因子,则primes[j]一定也是i * primes[j]的最小质因子,这样就确保了每个合数只会被它的最小质因子筛掉 
		}
	}
}

二. 因数

对于一个数 x(x[2,+)),可以写成如下形式:

x=p1α1×p2α2××pkαn

对于每一个因数 d,都可写成:

d=p1β1×p2β2××pkβn

由排列组合知识可知,x 的约数个数 num(x) 为:

(α1+1)×(α2+1)××(αk+1)

约数之和 sum(x) 为:

(p10+p11+p12++p1α1)×(p20+p21+p22++p2α2)×(pk0+pk1+pk2++pkαk)

根据等比数列求和公式可得:

sum(x)=i=1kpiαi+11pi1

试除法分解因数

因为一个数 n 的因数个数为 O(n),所以时间复杂度为 O(n)

Code:

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

int n;

vector <int> get(int x) {
    vector <int> res;
    for(int i = 1; i <= x / i; i++) {
        if(x % i == 0) {
            res.push_back(i);
            if(i != x / i) res.push_back(x / i);
        }
    }
    sort(res.begin(), res.end());
    return res;
}

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        int x;
        scanf("%d", &x);
        vector <int> ans = get(x);
        for(auto i : ans) printf("%d ", i);
        puts("");
    }
    
    return 0;
}

最大公因数

定义:

dadb,则 da,b 的公因数,其中满足条件的最大的 d 就是最大公因数,记为 gcd(a,b)

最小公倍数

定义:

ambm,则 ma,b 的公倍数,其中满足条件的最小的 m 就是最小公倍数,记为 lcm(a,b)

几个有意思的定理

  1. gcd(a,b)lcm(a,b)=ab

  2. a,b 的所有公因数都是最大公因数的因数,所有公倍数都是最小公倍数的倍数。

证明:

考虑将 a,b 分解质因数,并将质因数对齐,得:

a=p1a1p2a2pkak

b=p1b1p2b2pkbk

再将 gcd(a,b)=faclcm(a,b)=m 分解质因数,得:

fac=p1c1p2c2pkck

m=p1d1p2d2pkdk

注意!这里的 ai,bi,ci,di 可能为 0!(因为可能不含此质因子)

根据定义可知:

ci=min(ai,bi),di=max(ai,bi),i[1,k]

对于每个质因子 pi 来看,dm 相乘即为:

pimin(ai,bi)pimax(ai,bi)=piai+bi

所以总的乘起来就是 ab

证明完毕。

2 的话顺着这个继续证,很容易就证出来了。

1 可知,只用知道其中一种的计算方法就可以直接推出另一种,所以只用考虑 gcd 该怎么计算。

若直接朴素计算 gcd,最先想到的方式就是分解出其中一个数的所有因数,然后找最大的且能整除另一个数的因子,时间复杂度 O(n)

还有更快的方法吗?

九章算术 · 更相减损术

内容:

a,bN,ab,gcd(a,b)=gcd(b,ab)=gcd(a,ab)

证明:

对于 a,b 的任意公因数 d,由于 da,db,所以 d(ab)。因此,d 也是 b,ab 的公因数。所以 a,b 的公因数集合与 b,ab 的公因数集合相同,那么最大公因数自然也就相同。对与 a,ab 同理。

证明完毕。

这种方法当 a,b 相差不大时效率较高,但差距一旦过大,一直减也减不完,效率大打折扣。

但是这个方法可以扩展到 n 个数,即:

gcd(a1,a2,,an)=gcd(a1,a2a1,a3a2,,anan1)

欧几里得算法

我们发现上一种方法一次一次地减效率太低了,干脆一次就减完,即取模。

欧几里得算法就是在这个方面进行了优化。

内容:

a,bN,b0,gcd(a,b)=gcd(b,amodb)

证明:

和上一种方法如出一辙。

a<b,则上式显然成立。

ab,不妨设 a=kb+r,其中 0r<b。显然 r=amodb

对于 a,b 的任意公约数 d,由于 da,d(kb),所以 d(akb),即 dr。因此 d 也是 b,r 的公因数。所以 a,b 的公因数集合和 b,amodb 的公因数集合相同,最大公约数自然也就相等了。

证明完毕。

每次我们将 gcd(a,b) 变成了 gcd(b,amodb),分析一下括号里两个变量的变化。

  1. a<b 时,gcd(a,b)gcd(b,a),就变成下面情况 23

  2. ba<2b 时,gcd(a,b)=gcd(b,ab) 进一步分析就会发现,括号中的第二个元素至少减少了一半。

  3. a2b,括号中的第二个元素 bamodb 一定小于 b 也至少减少了一半。

综上所述,每次迭代第二个元素都会至少减半,所以时间复杂度是 log 级别的。具体的,时间复杂度应该是 O(logmax(a,b))

一般的,对于多个数 a1,a2,,an,做欧几里得算法的时间复杂度为 O(n+logmax1in{ai})

直接上代码:

int gcd(int x, int y) { //最大公因数
	if(y == 0) return  x;
	return gcd(y, x % y);
}

int lcm(int x, int y) { //最小公倍数
	return x * y / gcd(x, y);
}

欧拉函数

定义:

欧拉函数就是指对于 m 来讲,在 1m 中与 m 互素的数的个数,例如,当 n=8 时,与 8 互质的数分别是 1,3,5,7,因此 φ(8)=4

对于 {pn},{αn}, mN± 来讲,满足 m=1jnnpjαj (即 pn 为其质因子)

φ(m)=m1jnn(11pj).

证明在这里

套公式代码:(时间复杂度:O(n)

#include <iostream>
using namespace std;
int n;
int main() {
    scanf("%d", &n);
    while(n--) {
        int x;
        scanf("%d", &x);
        
        int res = x;
        for(int i = 2; i <= x / i; i++) { //分解质因数
            if(x % i == 0) {
                res = res / i * (i - 1);
                while(x % i == 0) x /= i;
            }
        }
        if(x > 1) res = res / x * (x - 1);
        
        printf("%d\n", res);
    }
    return 0; //直接套公式也没什么好说的
}

若要求 1n 中所有数的欧拉函数,一个一个求的时间复杂度为 O(nn),效率较低。

其实可以在线性筛质数时顺便求欧拉函数。

首先需要了解积性函数。

定义:

a,b 互质且有 f(ab)=f(a)f(b),则称 f 是积性函数。

基本性质:

f 是积性函数,则 n=i=1mpici,则 f(n)=i=1mf(pici)

证明:

n 分解质因数,对每个质因子都使用积性函数的定义,此性质显然成立。

接着我们需要欧拉函数的几个性质:

  1. 欧拉函数是积性函数。

  2. p 为质数,若 pnp2n,则 φ(n)=φ(n/p)p

  3. p 为质数,若 pnp2n,则 φ(n)=φ(n/p)(p1)

证明:

性质 1

要证欧拉函数是积性函数,即证:有 a,b 互质且有 φ(ab)=φ(a)φ(b)

因为 gcd(a,b)=1,所以 a,b 的质因数一定没有一个相同,将 a,b 分解质因数。

a=p1c1p2c2pkck

b=q1d1q2d2qmdm

φ(a)=a(11p1)(11p2)(11pk)

φ(b)=b(11q1)(11q2)(11qm)

φ(ab)=(ab)[(11p1)(11p2)(11pk)][(11q1)(11q2)(11qm)]

φ(ab)=φ(a)φ(b)

性质 1 得证。

性质 2

pnp2n,则 n,n/p 拥有相同的质因子,只是指数不同罢了,但是欧拉函数的计算只看有没有这个质因子而不关心指数。设 n,n/p 的质因子都为 p1,p2,,pk,则

φ(n)=n(11p1)(11p2)(11pk)

φ(n/p)=(n/p)(11p1)(11p2)(11pk)

两者相除,商为 p
性质 2 得证。

性质 3

pnp2n,则 n,n/p 互质,根据性质 1 可得 φ(n)=φ(n/p)φ(p),而 φ(p)=p1,所以 φ(n)=φ(n/p)(p1)

性质 3 得证。

综上所述,我们可以把上述性质与线性筛法结合起来。

具体地,每个合数 x 只会被它的最小质因子 p 筛一次,恰好在此时判断,从 φ(x/p) 递推到 φ(n)

时间复杂度为 O(n)

void get_eulers() {
    phi[1] = 1;
    for(int i = 2; i <= n; i++) {
        if(!st[i]) {
            prime[++cnt] = i;
            phi[i] = i - 1; //若是质数就直接求出欧拉函数
        }
        for(int j = 1; j <= cnt && prime[j] <= n / i; j++) {
            st[i * prime[j]] = true;
            if(i % prime[j] == 0) {
                phi[i * prime[j]] = phi[i] * prime[j]; //此时 primes[j] 是 i 的最小质因子,即此时满足性质 2
                break;
            }
            phi[i * prime[j]] = phi[i] * (prime[j] - 1); //否则满足性质 3
        }
    }
}

三. 同余

概念:

数学上,同余(congruence modulo,符号: ) 是数论中的一种等价关系。当两个整数除以同一个正整数,若得相同余数,则两个整数同余。

同余运算的常见性质

  1. ab(modp)p(ab)

  2. ab(modm),ab(modn)ab(mod[m,n])

  3. (k,m)=d,kakb(modm)ab(modmd)

证明:

对于性质 1

根据定义,设 a=k1p+r,b=k2p+r,所以 ab=(k1k2)p,所以 p(ab)

证毕。

对于性质 2

根据性质 1 得:n(ab),m(ab),所以 [m,n](ab),即 ab(mod[m,n])

证毕。

对于性质 3

根据性质 1 得:mk(ab),因为 gcd(k,m)=d,所以 mdkd(ab),又因为 gcd(md,kd)=1,所以 md(ab),即ab(modmd)

证毕。

基本知识 —— 关于 mod

  • 同余类/剩余类

由对于模 n 同余的所有整数组成的这个集合称为同余类congruence classresidue class),或称剩余类

可知,模 n 的同余类一共有 n 个,分别是模 n0,1,n1 的这 n 个。

如果模 m 的一个剩余类里所有数都与 m 互素,就把它叫做与模 m 互素的剩余类,比如: 5 的一个剩余系为 {4,9,14,,5k+4(kZ)},其中所有数都与 5 互素,所以此剩余系是与 5 互素的剩余系。

  • 完全剩余系:

从模 n每个剩余类中各取一个数,得到一个由 n 个数组成的集合,叫做模 n 的一个完全剩余系,简称完系

显然,完系中的 n 个数分别属于 n 个不同的剩余类。比如 {0,1,2,3,4}5 的一个完系,{5,6,7,8,9} 也是 5 的一个完系。

  • 最小非负完全剩余系

最简单的模 n 的完全剩余系是 {1,2,3,,n1} 也叫作模 n最小非负完系

  • 简化剩余系

简化剩余系( reduced residue system ) 也称既约剩余系缩系在与模 m 互素的全体剩余类中,从每一个类中各任取一个数作为代表组成的集合,叫做模 m 的一个简化剩余系

有无穷多个简化剩余系。例如,模 5 的一个简化剩余系是 {1,2,3,4},模 10 的一个简化剩余系是 {1,3,7,9},模 18 的一个简化剩余系是 {1,5,7,11,13,17}

m 的完全剩余系中与 m 互素的数构成的子集也是缩系。

此时欧拉函数的真正定义才浮出水面:

欧拉函数( Euler's totient function )是对于任意正整数其最小非负完系的个数,用 φ() 来表示,即 当我们用
{a1,a2,,an} 来表示模 n 的最小非负完系时,显然 j,sN+,js,gcd(xj,m)=1 是成立的。而 s 即表示 {an}
的个数,此时

φ(m)=s


费马小定理

内容:

p 是质数,则对于任意整数 a,有 ap11 (mod n)

欧拉定理

内容:

若正整数 x,n,则 xφ(n)1 (mod n),其中 φ(n) 为欧拉函数。

证明:

{a1,a2,,aφ(n)}n 的一个缩系,且 1a1,a2,,aφ(n)n,换句话说,这些数都是 1n 中与 n 互质的数,且两两不同。

x,n 互质。

xa1,xa2,,xaφ(n) 也都与 n 互质,且两两不同。

集合 {xa1,xa2,,xaφ(n)} 和集合 {a1,a2,,aφ(n)} 在模 n 的条件下完全等价,即:

(xa1)(xa2)(xaφ(n))a1a2aφ(n)

xφ(n)(a1a2aφ(n))a1a2aφ(n)

这里就要用到一个同余运算的法则了:若 acbc (mod n),且 gcd(c,n)=1,那么 ab

证明:根据同余的定义,原式可以写成 ackn=bctn (k,tZ),整理得:(ab)c=(kt)n

LHS 需要含有因子 n

gcd(c,n)=1

n(ab)

ab,证明完毕。

回到原题,由于 a1,a2,,aφ(n) 都与 n 互质,所以 a1a2aφ(n) 也与 n 互质,则可以消掉,得到 xφ(n)1 (mod n)

Q.E.D

那么当模数 n 为质数时,由于 φ(n)=n1,所以费马小定理就是欧拉定理的一个特殊情况,也能得证。

裴蜀定理

内容:

对于任意整数 a,b,设 d=gcd(a,b),则对于所有整数 x,y 都有 d(ax+by)。特别的,一定存在一对整数 x,y,满足 ax+by=d

证明:

第一点是显然的,因为 da,db,所以 dax,dby,d(ax+by)

对于第二点:

  1. b=0 时,显然当 x=1,y=0 时原式成立。

  2. b>0 时,则 gcd(a,b)=gcd(b,amodb)。假设存在一对整数 x,y,满足 bx+(amodb)y=gcd(b,amodb),因为 bx+(amodb)y=bx+(abab)y=ay+b(xaby),所以进一步令 x=y,y=xaby,就得到了 ax+by=gcd(a,b)

接着对欧几里得算法的递归过程使用数学归纳法,可知第二点成立,也就是顺着欧几里得算法递归,最终会把 b 消成 0

证明完毕。

P4549 【模板】裴蜀定理

这道题就考察对裴蜀定理的思考深度了。

先看两个数的情况,假如要最小化 a1x1+a2x2 且要为正,根据裴蜀定理,原式的值一定是 gcd(a1,a2) 的倍数,所以取 gcd(a1,a2) 是最优的。

推广到 3 个数的情况,即最小化 a1x1+a2x2+a3x3 且为正,由上可知应最小化 gcd(a1,a2)+a3x3,此式子的最小正值应该是 gcd(gcd(a1,a2),a3)=gcd(a1,a2,a3)

事实上,由于 gcd(gcd(a1,a2,,an1),an)=gcd(a1,a2,,an),所以是可以直接推广到 n 个数的,换句话说,原题的答案就是 gcd(a1,a2,,an)

同时,裴蜀定理给出了整数 x,y 的计算方法,所以我们可以在执行欧几里得算法时顺便递归计算,这种计算方法被称为扩展欧几里得算法

Code:

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

注意 x,y 是引用传递,上述代码可求出二元一次不定方程 ax+by=gcd(a,b) 的一组特解 x0,y0,并返回 gcd(a,b)

对于更一般的方程 ax+by=c,它有解当且仅当 dc。所以在有解的情况下,先求出 ax+by=gcd(a,b) 的一组特解 x0,y0,然后 x0,y0 同乘上 cd 即是原方程的一组特解。

那么我们该怎么表示它的通解呢?

gcd(a,b)=d。对于 ax+by=d,假设我们已经得到它的一组特解 x0,y0,那么我们考虑将 x0 加上 val 后得到另一组新整数解 x,y,所以此时需要满足 b(aval) 才行,即 val=kbd,kZ,此时 y 应该减少 kad

综上所述,ax+by=d 的通解为:

x=x0+kbd,    y=y0kad (kZ)

推广到一般情况 ax+by=c,通解为:

x=cdx0+kbd,    y=cdy0+kad (kZ)

线性同余方程

定义:

形式化定义:形如:axb(modp) 的式子被称为线性同余方程,也称一次同余方程。

求解

根据同余运算的法则可得:p(axb),即 ax+py=b。根据裴蜀定理,此方程有解当且仅当 gcd(a,p)b

在有解时,先用扩展欧几里得算法求出方程 ax+py=gcd(a,p) 的一组特解 x0,y0,那么 x=bgcd(a,p)x0 就是原方程的一个解,通解则是所有模 pgcd(a,p)x 同余的整数。

乘法逆元

定义:

若整数 b,p 互质,并且 ba,则存在一个整数 x,使得 abax(modp)。称 xb 的模 p 乘法逆元,记为 b1(modp)

简单说来,逆元就是人们发现同余运算中有的时候会出现除法,而除法的计算又是比较复杂的,所以就想了一个办法将它转化为乘法,于是逆元应运而生。

因为 abab1abbb1,所以 bb11(modp)

p 是质数且 b<p 时,根据费马小定理得:bp11,所以 bb1bp1,因为 b,p 互质,所以方程两边同时消去 b,得:

b1bp2(modp)

所以,当模数 p 为质数时,bp2 即为 b 的乘法逆元。

意思是若模数时质数,就可以用快速幂来求逆元。

那么如果只保证 b,p 互质,那么就只能求解方程 bx1(modp) 得到逆元了。

P3811 【模板】模意义下的乘法逆元

若用上述方法一个一个地求逆元,时间复杂度为 O(nlogn),在这道题 n3×106 的超强数据下会光荣 TLE (其实快速幂加上巴雷特模乘能卡过)

其实要快速求出 1n 每个数的模 pp 为质数)乘法逆元,有一种线性的做法。

p=ki+r,0r<p,即 p 除以 ikr

所以有:

pki+r0(modp)

时刻铭记我们的目标是 i1,所以得想办法把它创造并分离出来。

两边同乘上 i1r1,得:

kr1+i10(modp)

i1=pi(pmodi)1(modp)

特别地,当 i=1 时,i1=1(modp)

然后就可以开始快乐地线性递推了。

Code:

inv[1] = 1; //注意初始化
for(int i = 2; i <= n; i++)
    inv[i] = p - (p / i) * inv[p % i] % p;

P5431 【模板】模意义下的乘法逆元 2

若要求 n 个不连续的数的逆元,也要求线性,阁下又该如何应对?

注:以下所有运算都是在 (modp) 的情况下进行。

设这 n 个数分别为 a1,a2,,an

那么它们的逆元分别为 1a1,1a2,1an

计算这 n 个数的前缀积 sum1,sum2,,sumn

有递推式:

1sumi=1sumi+1ai+1

1ai=1sumisumi1

所以我们可以先朴素求出 sumn 的逆元,然后从后往前线性递推求出每个 sumi 的逆元,进而求出 ai 的逆元。

Code:

    sum[0] = 1;
    for(int i = 1; i <= n; i++) {
        a[i] = read<ll>();
        sum[i] = (sum[i - 1] * a[i]) % p;
    }
    ll x0, y0;
    exgcd(sum[n], p, x0, y0);
    inv_sum[n] = (x0 % p + p) % p;
    for(int i = n - 1; i; i--)
        inv_sum[i] = (inv_sum[i + 1] * a[i + 1]) % p;
    ll tmp = k, res = 0;
    for(int i = 1; i <= n; i++) {
        res = (res + tmp * inv_sum[i] % p * sum[i - 1] % p) % p;
        tmp = tmp * k % p;
    }

中国剩余定理 (CRT)

内容:

m1,m2,,m3 是两两互质的整数,m=i=1nmi,Mi=m/mi,ti 是线性同余方程 Miti1(modmi) 的一个解。对于任意的 n 个整数 a1,a2,,an,方程组

posted @   Brilliant11001  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示