Skip to content

数学

向上取整

ab=a+b1b

C++
int ceil = (a + b - 1) / b
int ceil = (a + b - 1) / b

加法原理

完成一个工程可以有 n 类办法, ai(1in) 代表第 i 类方法的数目。那么完成这件事共有 S=a1+a2++an 种不同的方法。

乘法原理

完成一个工程需要分 n 个步骤,ai(1in) 代表第 i 个步骤的不同方法数目。那么完成这件事共有 S=a1×a2××an种不同的方法。

算术基本定理

任何一个大于 1正整数都能分解为有限个质数的乘积:

n=p1α1×p2α2××pkαk,p1<p2<<pk

其中 αiZpi 都是质数,且 p1<p2<pk ;在不计次序的意义下,该表示唯一。

裴蜀定理

裴蜀定理,又称贝祖定理(Bézout's lemma)。是一个关于最大公约数的定理。

其内容是:

a,b不全为零的整数,则存在整数 x,y ,使得 ax+by=gcd(a,b)

平方和公式

i=1ni2=12+22++n2=n(n+1)(2n+1)6

质数

试除法判质数

模板代码:

Java
boolean isPrime(int n) {
    if (n < 2) return false;
    for (int x = 2; x <= n / x; ++x) {
        if (n % x == 0) return false;
    }
    return true;
}
boolean isPrime(int n) {
    if (n < 2) return false;
    for (int x = 2; x <= n / x; ++x) {
        if (n % x == 0) return false;
    }
    return true;
}
C++
bool is_prime(int n) {
    if (n < 2) return false;
    for (int x = 2; x <= n / x; ++x) {
        if (n % x == 0) return false;
    }
    return true;
}
bool is_prime(int n) {
    if (n < 2) return false;
    for (int x = 2; x <= n / x; ++x) {
        if (n % x == 0) return false;
    }
    return true;
}

时间复杂度:O(n)

试除法分解质因数

分解质因数(Prime Factorization):根据算术基本定理,每个合数都可以写成几个质数相乘的形式,其中每个质数都是这个合数的因数,把一个合数用质因数相乘的形式表示出来,叫做分解质因数。

模板题:【数学】分解质因数

原理:试除法,枚举 2n 中的所有质数,如果 in质因子,就把 n 中的 i 除干净。

模板代码:

Java
void pf(int n) {
    for (int x = 2; x <= n / x; ++x) {
        if (n % x == 0) {
            int s = 0;
            while (n % x == 0) {
                n /= x;
                ++s;
            }
            System.out.println(x + " " + s);
        }
    }
    if (n > 1) System.out.println(n + " 1");
}
void pf(int n) {
    for (int x = 2; x <= n / x; ++x) {
        if (n % x == 0) {
            int s = 0;
            while (n % x == 0) {
                n /= x;
                ++s;
            }
            System.out.println(x + " " + s);
        }
    }
    if (n > 1) System.out.println(n + " 1");
}
C++
void pf(int n) {
    for (int x = 2; x <= n / x; ++x) {
        if (n % x == 0) {
            int s = 0;
            while (n % x == 0) {
                n /= x;
                ++s;
            }
            printf("%d %d\n", x, s);
        }
    }
    if (n > 1) printf("%d 1\n", n);
}
void pf(int n) {
    for (int x = 2; x <= n / x; ++x) {
        if (n % x == 0) {
            int s = 0;
            while (n % x == 0) {
                n /= x;
                ++s;
            }
            printf("%d %d\n", x, s);
        }
    }
    if (n > 1) printf("%d 1\n", n);
}

时间复杂度:

  • 最差: O(n)
  • 最好: O(logN)

其中枚举的 i 一定是质数,因为:当枚举到 i 的时候,已经把 [2,i1] 中的质因子都除掉了,当 if (n % i == 0) 成立的时候,n 中已经没有任何在 [2,i1] 范围内的质因子,又因为 ni 的倍数,所以 i 也没有任何在 [2,i1] 范围内的因子,所以此时 i 一定是质数。

筛质数

模板题:【数学】筛质数

朴素筛

原理:

  • 枚举 x[2,n]x 的倍数一定是合数,所以可以在枚举到 x 的时候筛掉所有 小于等于 nx 的倍数
  • 而在枚举到 p[2,n] 的时候,如果 p 还没有被筛掉,说明 p 不是 x[2,p1] 中任意一个数的倍数,所以 p 一定是质数。

模板代码:

Java
int countPrimes(int n) {
    int cnt = 0;
    int[] primes = new int[n + 1];
    boolean[] isNotPrime = new boolean[n + 1];
    for (int x = 2; x <= n; ++x) {
        if (!isNotPrime[x]) primes[cnt++] = x;
        for (int k = 2; k * x <= n; ++k) isNotPrime[k * x] = true;
    }
    return cnt;
}
int countPrimes(int n) {
    int cnt = 0;
    int[] primes = new int[n + 1];
    boolean[] isNotPrime = new boolean[n + 1];
    for (int x = 2; x <= n; ++x) {
        if (!isNotPrime[x]) primes[cnt++] = x;
        for (int k = 2; k * x <= n; ++k) isNotPrime[k * x] = true;
    }
    return cnt;
}

时间复杂度:O(NlogN)

埃氏筛

原理:要得到自然数 n 以内的全部质数,把不大于 n 的所有质数倍数筛除,剩下的就都是质数。

证明:根据算术基本定理每一个大于 1 的正整数都能分解成有限个质数的幂的乘积,且由于是从小到大枚举,在枚举到某个合数之前,一定先会枚举到它的质因数,也就是说所有的合数都会被它的质因数筛掉。

New_Animation_Sieve_of_Eratosthenes.gif

模板代码:

Java
int countPrimes(int n) {
    int cnt = 0;
    int[] primes = new int[n + 1];
    boolean[] isNotPrime = new boolean[n + 1];
    for (int x = 2; x <= n; ++x) {
        if (!isNotPrime[x]) {
            primes[cnt++] = x;
            for (int k = 2; k * x <= n; ++k) isNotPrime[k * x] = true;
        }
    }
    return cnt;
}
int countPrimes(int n) {
    int cnt = 0;
    int[] primes = new int[n + 1];
    boolean[] isNotPrime = new boolean[n + 1];
    for (int x = 2; x <= n; ++x) {
        if (!isNotPrime[x]) {
            primes[cnt++] = x;
            for (int k = 2; k * x <= n; ++k) isNotPrime[k * x] = true;
        }
    }
    return cnt;
}

时间复杂度:O(NloglogN)

线性筛(欧拉筛)

原理:线性筛保证了每个合数都只被其最小质因子筛掉。

模板代码:

Java
int countPrimes(int n) {
    int cnt = 0;
    int[] primes = new int[n + 1];
    boolean[] isNotPrime = new boolean[n + 1];
    for (int x = 2; x <= n; ++x) {
        if (!isNotPrime[i]) primes[cnt++] = x;
        for (int i = 0; primes[i] <= n / x; ++i) {
            isNotPrime[primes[i] * x] = true;
            if (x % primes[i] == 0) break;
        }
    }
    return cnt;
}
int countPrimes(int n) {
    int cnt = 0;
    int[] primes = new int[n + 1];
    boolean[] isNotPrime = new boolean[n + 1];
    for (int x = 2; x <= n; ++x) {
        if (!isNotPrime[i]) primes[cnt++] = x;
        for (int i = 0; primes[i] <= n / x; ++i) {
            isNotPrime[primes[i] * x] = true;
            if (x % primes[i] == 0) break;
        }
    }
    return cnt;
}
C++
int cnt, primes[N];
bool np[N];

int sieve(int n) {
    for (int x = 2; x <= n; ++x) {
        if (!np[x]) primes[cnt++] = x;
        for (int i = 0; primes[i] <= n / x; ++i) {
            np[primes[i] * x] = true;
            if (x % primes[i] == 0) break;
        }
    }
    return cnt;
}
int cnt, primes[N];
bool np[N];

int sieve(int n) {
    for (int x = 2; x <= n; ++x) {
        if (!np[x]) primes[cnt++] = x;
        for (int i = 0; primes[i] <= n / x; ++i) {
            np[primes[i] * x] = true;
            if (x % primes[i] == 0) break;
        }
    }
    return cnt;
}

时间复杂度:O(N)

线性筛是如何保证每个合数只会被其最小质因子筛掉的?

  • x % primes[i] == 0 时,说明此时遍历到的 pi 不是 x 的质因子,那么 **pi 一定小于 x 的最小质因子 **,所以 pix 的最小质因子就是 pi
  • x % primes[i] != 0 时 ,说明 x 的最小质因子是此时的 pi ,因此 pix 的最小质因子依旧应该是 pi ,但如果继续枚举的话,我们就把 pi+1x 这个数筛掉了,虽然这个数也是合数,但是筛掉它的时候并不是用它的最小质因数筛掉的 ,而是利用 pi+1x 把它删掉的,而这个数的最小质因数其实是 pi ,如果不在这里退出循环的话,就会发现有些数是被重复筛了的。

勒让德定理

在正数 n! 的质因数分解中,质数 p 的指数记作 νp(n!) ,则:

νp(n!)=i=1logpnnpk

模板代码:

C++
int count(int n, int p) {
    int res = 0;
    while (n) res += n /= p;
    return res;
}
int count(int n, int p) {
    int res = 0;
    while (n) res += n /= p;
    return res;
}

约数

试除法求所有约数

模板代码:

C++
set<int> get_divisors(int n) {
    set<int> divisors;
    for (int i = 1; i <= n / i; ++i) {
        if (n % i == 0) {
            divisors.insert(i);
            divisors.insert(n / i);
        }
    }
    return divisors;
}
set<int> get_divisors(int n) {
    set<int> divisors;
    for (int i = 1; i <= n / i; ++i) {
        if (n % i == 0) {
            divisors.insert(i);
            divisors.insert(n / i);
        }
    }
    return divisors;
}

时间复杂度:O(n)

int 范围内,约数最多的数其约数有 1536 个。

约数个数

求约数个数

模板题:【数学】约数个数

原理:

  • 根据算术基本定理n 质因数分解为:n=p1α1×p2α2××pkαk
  • 那么 n 的约数个数为:f(n)=i=1k(αi+1)=(α1+1)(α2+1)(αk+1)

证明:

  • 显然, p1α1 的约数为 p10,p11,,p1k ,同理: piαi 的约数为 pi0,pi1,,pii
  • 实际上 n 的约数是在 p1α1,p2α2,pkαk 每个数的约数分别挑一个相乘得来的,那么 n 的每个约数就都可以被分解为 d=p1β1×p2β2××pkβk (其中 0βiαi );
  • 又根据算术基本定理:每一个数的质因数分解是唯一的,那么 βi 的所有组合就对应了 n 的所有约数
  • 而每个 βi 都有 0αiαi+1 种选择,所以 d 共有 i=1k(αi+1) 个组合,那么 n 也就有这么多个约数。

模板代码:

C++
// 求一个数的约数个数
int count_divisors(int n) {
    int cnt = 1;
    for (int i = 2; i <= n / i; ++i) {
        if (n % i == 0) {
            int s = 0;
            while (n % i == 0) {
                n /= i;
                ++s;
            }
           	cnt *= s + 1;
        }
    }
    if (n > 1) cnt *= 2;
    return cnt;
}
// 求一个数的约数个数
int count_divisors(int n) {
    int cnt = 1;
    for (int i = 2; i <= n / i; ++i) {
        if (n % i == 0) {
            int s = 0;
            while (n % i == 0) {
                n /= i;
                ++s;
            }
           	cnt *= s + 1;
        }
    }
    if (n > 1) cnt *= 2;
    return cnt;
}

时间复杂度:

  • 最差: O(n)
  • 最好: O(logN)
一些与约数个数相关的性质
  • 1N 中所有数的约数个数之和总和约为 NlogN 个;
  • [0,2×109] 范围内的约数个数最多的数的约数约有 1600 个。

约数之和

模板题:【数学】约数之和

原理:

  • 根据算术基本定理n 质因数分解为:n=p1α1×p2α2××pkαk

  • 那么 n 的约数之和为:

    σ(n)=i=1k(j=0αkpij)=(p10+p11+p12++p1α1)(p20+p21+p22++p2α2)(pk0+pk1+pk2++pkαk)

证明:

  • 显然, p1α1 的约数为 p10,p11,,p1k ,同理: piαi 的约数为 pi0,pi1,,pii

  • 实际上 n 的约数是在 p1α1,p2α2,pkαk 每个数的约数分别挑一个相乘得来的;

  • 可知共有 (α1+1)(α2+1)(αk+1) 种挑法,即约数个数。

  • 乘法原理可知它们的和为:

    σ(n)=i=1k(j=0αkpij)=(p10+p11+p12++p1α1)(p20+p21+p22++p2α2)(pk0+pk1+pk2++pkαk)

模板代码:

C++
// 求一个数的约数之和
int sum_divisors(int n) {
    int sum = 1;
    for (int i = 2; i <= n / i; ++i) {
        if (n % i == 0) {
			int tmp = 1;
            while (n % i == 0) {
                n /= i;
                tmp = i * tmp + 1;
            }
            sum *= tmp;
        }
    }
    if (n > 1) sum *= n + 1;
    return sum;
}
// 求一个数的约数之和
int sum_divisors(int n) {
    int sum = 1;
    for (int i = 2; i <= n / i; ++i) {
        if (n % i == 0) {
			int tmp = 1;
            while (n % i == 0) {
                n /= i;
                tmp = i * tmp + 1;
            }
            sum *= tmp;
        }
    }
    if (n > 1) sum *= n + 1;
    return sum;
}

最大公约数

原理:辗转相除法(欧几里得算法): gcd(a,b)=gcd(b,amodb)

证明:

  • 已知 amodb=aabb ,设 c=ab ,则现在要证明 gcd(a,b)=gcd(b,acb)
  • 对于 gcd(a,b) , 设 da,db
    • 则显然有 dax+by,x,yZ ,当 x=1,y=c 时, dacb
    • db,dacb 同时成立,则有 gcd(a,b)gcd(b,acb)
  • 对于 gcd(b,acb) ,设 db,dacb
    • 则显然有 dacb+cbda
    • da,db 同时成立,则有 gcd(b,acb)gcd(a,b)
  • gcd(a,b)=gcd(b,acb) 得证,同时 gcd(a,b)=gcd(b,amodb) 也成立。

模板代码:(递归实现)

C++
int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}
int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

欧拉函数

公式求欧拉函数

详细介绍及原理:欧拉函数 - OI Wiki

若在算术基本定理中:n=p1α1×p2α2××pkαk

那么 n 的欧拉函数:ϕ(n)=n×i=1k(11pi)=n×p11p1×p21p2××pk1pk

模板代码:

C++
typedef long long LL;

// 求一个数的欧拉函数
LL phi(int n) {
    LL res = n;
    for (int x = 2; x <= n / x; ++x) {
        if (n % x == 0) {
            while (n % x == 0) n /= x;
            res = res * (x - 1) / x;
        }
    }
    if (n > 1) res = res * (n - 1) / n;
    return res;
}
typedef long long LL;

// 求一个数的欧拉函数
LL phi(int n) {
    LL res = n;
    for (int x = 2; x <= n / x; ++x) {
        if (n % x == 0) {
            while (n % x == 0) n /= x;
            res = res * (x - 1) / x;
        }
    }
    if (n > 1) res = res * (n - 1) / n;
    return res;
}

时间复杂度: O(N)

筛法求欧拉函数之和

1n 中每个数的欧拉函数之和:

Java
long sumEulers(int n) {
    int cnt = 0;
    int[] primes = new int[n + 1], phi = new int[n + 1];
    boolean[] isNotPrime = new boolean[n + 1];
    phi[1] = 1;
    for (int i = 2; i <= n; ++i) {
        if (!isNotPrime[i]) {
            primes[cnt++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; primes[j] <= n / i; ++j) {
            isNotPrime[primes[j] * i] = true;
            if (i % primes[j] == 0) {
                phi[primes[j] * i] = phi[i] * primes[j];
                break;
            }
            phi[primes[j] * i] = phi[i] * (primes[j] - 1);
        }
    }
    long res = 0L;
    for (int i = 1; i <= n; ++i) res += phi[i];
    return res;
}
long sumEulers(int n) {
    int cnt = 0;
    int[] primes = new int[n + 1], phi = new int[n + 1];
    boolean[] isNotPrime = new boolean[n + 1];
    phi[1] = 1;
    for (int i = 2; i <= n; ++i) {
        if (!isNotPrime[i]) {
            primes[cnt++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; primes[j] <= n / i; ++j) {
            isNotPrime[primes[j] * i] = true;
            if (i % primes[j] == 0) {
                phi[primes[j] * i] = phi[i] * primes[j];
                break;
            }
            phi[primes[j] * i] = phi[i] * (primes[j] - 1);
        }
    }
    long res = 0L;
    for (int i = 1; i <= n; ++i) res += phi[i];
    return res;
}

时间复杂度: O(N)

快速幂

详细介绍及原理:快速幂 - OI Wiki

模板代码:

Java
long kasumi(long base, long exp, long mod) {
    long res = 1L;
    while (exp != 0) {
        if ((exp & 1) != 0) res = res * base % mod;
        base = base * base % mod;
        exp >>= 1;
    }
    return res;
}
long kasumi(long base, long exp, long mod) {
    long res = 1L;
    while (exp != 0) {
        if ((exp & 1) != 0) res = res * base % mod;
        base = base * base % mod;
        exp >>= 1;
    }
    return res;
}
C++
typedef long long LL;

LL kasumi(LL base, LL exp, LL mod) {
    LL res = 1LL;
    while (exp) {
        if (exp & 1) res = res * base % mod;
        base = base * base % mod;
        exp >>= 1;
    }
    return res;
}
typedef long long LL;

LL kasumi(LL base, LL exp, LL mod) {
    LL res = 1LL;
    while (exp) {
        if (exp & 1) res = res * base % mod;
        base = base * base % mod;
        exp >>= 1;
    }
    return res;
}

慢速乘

防止乘数及模数很大但还在 long long 范围内时直接做乘爆 long long 的问题。

Java
long slowMul(long a, long b, long m) {
    long c = 0L;
    while (b > 0) {
        if ((b & 1) != 0) c = (c + a) % m;
        a = (a + a) % m;
        b >>= 1;
    }
    return c;
}
long slowMul(long a, long b, long m) {
    long c = 0L;
    while (b > 0) {
        if ((b & 1) != 0) c = (c + a) % m;
        a = (a + a) % m;
        b >>= 1;
    }
    return c;
}
C++
typedef long long LL;

LL slow_mul(LL a, LL b, LL m) {
    LL c = 0LL;
    while (b) {
        if (b & 1) c = (c + a) % m;
        a = (a + a) % m;
        b >>= 1;
    }
    return c;
}
typedef long long LL;

LL slow_mul(LL a, LL b, LL m) {
    LL c = 0LL;
    while (b) {
        if (b & 1) c = (c + a) % m;
        a = (a + a) % m;
        b >>= 1;
    }
    return c;
}

扩展欧几里得算法

扩展欧几里得算法(Extended Euclidean algorithm, EXGCD),常用于求 ax+by=gcd(a,b)裴蜀定理)的一组可行解。

过程

设:

ax1+by1=gcd(a,b)

bx2+(amodb)y2=gcd(b,amodb)

欧几里得算法可知: gcd(a,b)=gcd(b,amodb)

所以:

ax1+by1=bx2+(amodb)y2ax1+by1=bx2+(aabb)y2ax1+by1=bx2+ay2baby2ax1+by1=ay2+b(x2aby2)

因为 a=a,b=b ,所以:{x1=y2y1=x2aby2

x2,y2 不断代入递归求解,直至 b=0 时: ax+0y=a ,显然 {x=1y=0 是一组解,此时退出递归。

模板代码

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

线性同余方程

定义

形如:

axb(modn)

的方程称为 线性同余方程(Congruence Equation)。其中,