一直以来,没能系统学习算法,是我心中的痛;现在学了 y 总的算法基础课,做做笔记,希望能够有所收获。
这是第五节,数学知识。
尽管很多算法之前在离散数学中经常使用,但是从 OI 的视角来看,又是一种全新的感觉……
数学知识
质数
试除法判定质数
时间复杂度 O(N)
模板
1 2 3 4 5 6 7
boolisPrime(int x){ if (x < 2) returnfalse; for (int i = 2; i <= x / i; i ++ ) // 使用 x / i 效果最好,注意是 <= if (x % i == 0) returnfalse; returntrue; }
试除法分解质因数
时间复杂度 O(N)
模板
1 2 3 4 5 6 7 8
voiddivide(int x){ for (int i = 2; i <= x / i; i ++ ){ int s = 0; while (x % i == 0) x /= i, s ++; cout << i << ' ' << s << endl; } if (x > 1) cout << x << ' ' << 1 << endl; }
求素数
朴素筛法
时间复杂度 O(NlogN)
模板
1 2 3 4 5 6 7 8 9 10 11 12
int primes[N], cnt; // primes[] 存储所有素数 bool st[N]; // st[x] 存储 x 是否被筛掉
voidget_primes(int n){ for (int i = 2; i <= n; i ++ ){ if (!st[i]) primes[cnt ++ ] = i; for (int j = i + i; j <= n; j += i){ st[j] = true; } } }
埃氏筛
时间复杂度 O(NloglogN)
模板
1 2 3 4 5 6 7 8 9 10 11 12
int primes[N], cnt; // primes[] 存储所有素数 bool st[N]; // st[x] 存储 x 是否被筛掉 voidget_primes(int n){ for (int i = 2; i <= n; i ++ ){ if (!st[i]){ primes[cnt ++ ] = i; for (int j = i + i; j <= n; j += i){ st[j] = true; } } } }
线性筛
时间复杂度 O(N)
模板
1 2 3 4 5 6 7 8 9 10 11
int primes[N], cnt; // primes[] 存储所有素数 bool st[N]; // st[x] 存储 x 是否被筛掉 voidget_primes(int n){ for (int i = 2; i <= n; i ++ ){ if (!st[i]) primes[cnt ++ ] = i; for (int j = 0; primes[j] <= n / i; j ++ ){ st[primes[j] * i] == true; if (i % primes[j] == 0) break; } } }
约数
试除法求所有约数
模板
1 2 3 4 5 6 7 8 9 10 11
vector<int> get_divisors(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 范围内约数最多的个数约 1500 个
约数个数、约数之和
如果 N=p1c1∗p2c2∗⋯∗pkck
约数个数: (c1+1)∗(c2+1)∗⋯∗(ck+1)
约数之和: (p10+p11+⋯+p1c1)∗⋯∗(pk0+pk1+⋯+pkck)
欧几里得算法求最大公约数
模板
1 2 3
intgcd(int a, int b){ return b ? gcd(b, a % b) : a }
欧拉函数
N=p1a1p2a2…pmam,则 ϕ(N)=N×p1p1−1×p2p2−1×…×pmpm−1。 ϕ(N) 是 1 ~ N 与 N 互质的数的个数,被称为欧拉函数。
容斥原理证明
计算欧拉函数
定义法
时间复杂度 O(N)
模板
1 2 3 4 5 6 7 8 9 10 11 12
intphi(int 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);
// a[N][N]是增广矩阵 intgauss(){ int c, r; for (c = 0, r = 0; c < n; c ++ ){ int t = r; for (int i = r; i < n; i ++ ){ if (fabs(a[i][c]) > fabs(a[t][c])) t = i; // 找到绝对值最大的一行 }
if (fabs(a[t][c]) < eps) continue; //如果第 c 列是 0, 就直接考虑下一列
for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将该行换到最上面
for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将该行第一个数变成 1
for (int i = r; i <= n; i ++ ) if (fabs(a[i][c]) > eps) for (int j = c; j >= c; j --) a[i][j] -= a[r][j] * a[i][c];
r ++; }
if (r < n){ for (int i = r; i < n; i ++ ){ if (fabs(a[i][n]) > eps) return2; // 无解 } return1; // 有无穷多组解 }
for (int i = n - 1; i >= 0; i -- ) for (int j = i + 1; j < n; j ++ ) a[i][n] -= a[i][j] * a[j][n]; return0; // 有唯一解 }
组合数
递归法求组合数
原理:Cab=Ca−1b+Ca−1b−1
时间复杂度 O(N2)
1≤b≤a≤2000
模板
1 2 3 4 5
// c[a][b] 表示从a个苹果中选b个的方案数 for (int i = 0; i < N; i ++ ) for (int j = 0; j <= i; j ++ ) if (!j) c[i][j] = 1; else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
int primes[N], cnt; // 存储所有质数 int sum[N]; //存储每个质数的次数 bool st[N]; // 存储每个数是否已被筛掉
// 线性筛 voidget_primes(int n){ for (int i = 2; i <= n; i ++){ if (!st[i]) primes[cnt ++ ] = i; for (int j = 0; primes[j] <= n / i; j ++ ){ st[primes[j] * i] = true; if (i % primes[j] == 0) break; } } }
// 求 n! 中素数 p 的次数 intget(int n, int p){ int res = 0; while (n){ res += n / p; n /= p; } return res; }
// 高精度乘法 vector<int> mul(vector<int> a, b){ vector<int> c; int t = 0; for (int i = 0; i < a.size(); i ++ ){ t += a[i] * b; c.push_back(t % 10); t /= 10; }
while (t){ c.push_back(t % 10); t /= 10; } return c; }
get_primes(a);
for (int i = 0; i < cnt; i ++ ){ int p = primes[i]; sum[i] = get(a, p) - get(b, p) - get(a - b, p); }
vector<int> res; res.push_back(1);
for (int i = 0; i < cnt; i ++ ) for (int j = 0; j < sum[i]; j ++ ) res = mul(res, primes[i]);
卡特兰数
通项公式: Cn=n+1C2nn
所有的卡特兰数问题经过一定的转换都可以还原成进出栈问题
举例:给定 n 个 0 和 n 个 1,它们按照某种顺序排成长度为 2n 的序列,满足任意前缀中 0 的个数都不少于 1 的个数的序列的数量