数学
向上取整
int ceil = (a + b - 1) / b
int ceil = (a + b - 1) / b
加法原理
完成一个工程可以有
乘法原理
完成一个工程需要分
算术基本定理
任何一个大于
其中
裴蜀定理
裴蜀定理,又称贝祖定理(Bézout's lemma)。是一个关于最大公约数的定理。
其内容是:
设
平方和公式
质数
试除法判质数
模板代码:
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;
}
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;
}
时间复杂度:
试除法分解质因数
分解质因数(Prime Factorization):根据算术基本定理,每个合数都可以写成几个质数相乘的形式,其中每个质数都是这个合数的因数,把一个合数用质因数相乘的形式表示出来,叫做分解质因数。
模板题:【数学】分解质因数。
原理:试除法,枚举
模板代码:
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");
}
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);
}
时间复杂度:
- 最差:
; - 最好:
。
其中枚举的 if (n % i == 0)
成立的时候,
筛质数
模板题:【数学】筛质数。
朴素筛
原理:
- 枚举
, 的倍数一定是合数,所以可以在枚举到 的时候筛掉所有 小于等于 的 的倍数 。 - 而在枚举到
的时候,如果 还没有被筛掉,说明 不是 中任意一个数的倍数,所以 一定是质数。
模板代码:
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;
}
时间复杂度:
埃氏筛
原理:要得到自然数
证明:根据算术基本定理:每一个大于
模板代码:
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;
}
时间复杂度:
线性筛(欧拉筛)
原理:线性筛保证了每个合数都只被其最小质因子筛掉。
模板代码:
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;
}
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;
}
时间复杂度:
线性筛是如何保证每个合数只会被其最小质因子筛掉的?
- 当
x % primes[i] == 0
时,说明此时遍历到的不是 的质因子,那么 ** 一定小于 的最小质因子 **,所以 的最小质因子就是 ; - 当
x % primes[i] != 0
时 ,说明的最小质因子是此时的 ,因此 的最小质因子依旧应该是 ,但如果继续枚举的话,我们就把 这个数筛掉了,虽然这个数也是合数,但是筛掉它的时候并不是用它的最小质因数筛掉的 ,而是利用 和 把它删掉的,而这个数的最小质因数其实是 ,如果不在这里退出循环的话,就会发现有些数是被重复筛了的。
勒让德定理
在正数
模板代码:
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;
}
约数
试除法求所有约数
模板代码:
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;
}
时间复杂度:
int
范围内,约数最多的数其约数有个。
约数个数
求约数个数
模板题:【数学】约数个数。
原理:
- 根据算术基本定理把
质因数分解为: ; - 那么
的约数个数为: 。
证明:
- 显然,
的约数为 ,同理: 的约数为 ; - 实际上
的约数是在 每个数的约数中分别挑一个相乘得来的,那么 的每个约数就都可以被分解为 (其中 ); - 又根据算术基本定理:每一个数的质因数分解是唯一的,那么
的所有组合就对应了 的所有约数; - 而每个
都有 共 种选择,所以 共有 个组合,那么 也就有这么多个约数。
模板代码:
// 求一个数的约数个数
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;
}
时间复杂度:
- 最差:
; - 最好:
。
一些与约数个数相关的性质
中所有数的约数个数之和总和约为 个; 范围内的约数个数最多的数的约数约有 1600 个。
约数之和
模板题:【数学】约数之和。
原理:
根据算术基本定理把
质因数分解为: ; 那么
的约数之和为:
证明:
显然,
的约数为 ,同理: 的约数为 ; 实际上
的约数是在 每个数的约数中分别挑一个相乘得来的; 可知共有
种挑法,即约数个数。 由乘法原理可知它们的和为:
模板代码:
// 求一个数的约数之和
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;
}
最大公约数
原理:辗转相除法(欧几里得算法):
证明:
- 已知
,设 ,则现在要证明 ; - 对于
, 设 : - 则显然有
,当 时, ; 同时成立,则有 。
- 则显然有
- 对于
,设 : - 则显然有
; 同时成立,则有 。
- 则显然有
得证,同时 也成立。
模板代码:(递归实现)
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。
若在算术基本定理中:
那么
模板代码:
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;
}
时间复杂度:
筛法求欧拉函数之和
求
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;
}
时间复杂度:
快速幂
详细介绍及原理:快速幂 - OI Wiki。
模板代码:
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;
}
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
的问题。
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;
}
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),常用于求
过程
设:
由欧几里得算法可知:
所以:
因为
将
模板代码
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;
}
线性同余方程
定义
形如:
的方程称为 线性同余方程(Congruence Equation)。其中,