第 8 章 数学
8.2 数论
8.2.1 模运算
- (a + b) % m = ((a % m) + (b % m)) % m
- (a - b) % m = ((a % m) - (b % m)) % m
- (a * b) % m = ((a % m) * (b % m)) % m
- 除法不适用
8.2.2 快速幂
8.2.2.1 分治法
// 用分治法求快速幂
#include <bits/stdc++.h>
using namespace std;
int fastPow(int a, int n){
if(n == 0) return 1;
if(n == 1) return a;
int temp = fastPow(a, n >> 1);
if(n % 2 == 1) return temp * temp * a;
else return temp * temp;
}
int main(){
int a, n;
cin >> a >> n;
cout << fastPow(a, n);
return 0;
}
8.2.2.2 位运算
// 用位运算求快速幂
#include <bits/stdc++.h>
using namespace std;
int fastPow(int a, int n){
int res = 1;
while(n){
if(n & 1) res *= a;
a *= a;
n >>= 1;
}
return res;
}
int main(){
int a, n;
cin >> a >> n;
cout << fastPow(a, n);
return 0;
}
8.2.2.3 对幂运算求模
// 对位运算求模
while(n){
if(n & 1) res = (res * a) % MOD;
a = a * a % MOD;
n >>= 1;
}
8.2.2.4 矩阵快速幂
// 对矩阵快速幂
#include <iostream>
#include <string.h>
using namespace std;
const int MAXN = 2;
const int MOD = 1e4;
struct Matrix{
int m[MAXN][MAXN];
Matrix(){
memset(m, 0, sizeof(m));
}
};
Matrix Mulit(Matrix a, Matrix b){
// 两个矩阵乘法
Matrix res;
for(int i = 0;i < MAXN;i ++)
for(int j = 0;j < MAXN;j ++)
for(int k = 0;k < MAXN;k ++)
res.m[i][j] = (res.m[i][j] + a.m[i][k] * b.m[k][j]) % MOD;
return res;
}
Matrix fastPow(Matrix a, int n){
// 快速幂
Matrix res;
for(int i = 0;i < MAXN;i ++)
res.m[i][i] = 1;
while(n){
if(n & 1) res = Mulit(res, a);
a = Mulit(a, a);
n >>= 1;
}
return res;
}
8.2.3 GCD LCM
8.2.3.1 最大公约数
// 求最大公约数
int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
// 用 C++ 内置函数求最大公约数
std:__gcd(a, b);
8.2.3.2 最小公倍数
// 求最小公倍数
int lcm(int a, int b){
return a / gcd(a, b) * b;
}
8.2.4 扩展欧几里得算法与二元一次方程的整数解
用扩展欧几里得算法求解 ax + by = gcd(a, b) 后,利用它可以进一步解任意方程 ax + by = n,得到一个整数解。步骤如下:
- 判断方程 ax + by = n 是否有整数解。有解的条件是 gcd(a, b) 可以整除 n;
- 用扩展欧几里得算法求 ax + by = gcd(a, b) 的一个解(x0, y0);
- 在 ax0 + by0 = gcd(a, b) 两边同时乘以 n / gcd(a, b),得:
ax0n / gcd(a, b) + by0gcd(a, b) = n; - 对照 ax + by = n ,得到它的一个解 (x0’, y0’) 是:
x0’ = x0n / gcd(a, b);
y0’ = y0n / gcd(a, b);
// 输入 a、b、n,求出符合条件的二元一次方程整数解
#include <bits/stdc++.h>
using namespace std;
void extend_gcd(int a,int b, int &x, int &y){
if(b == 0){
x = 1;
y = 0;
return;
}
extend_gcd(b, a % b, x, y);
int tmp = x;
x = y;
y = tmp - (a / b) * y;
}
int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
int main(){
int a, b, x, y, n;
cin >> a >> b >> n;
if(n % std::__gcd(a, b) != 0){
cout << "没有整数解" << endl;
return 0;
}
extend_gcd(a, b, x, y);
cout << "A: " << x * n / gcd(a, b) << endl;
cout << "B: " << y * n / gcd(a, b) << endl;
return 0;
}
8.2.5 同余与逆元
8.2.5.1 同余
- 如果 a % m == b % m,则称 a 和 b 对于 m 同余,m 称同余的模。
- 同样理解为:m | (a - b),即 a - b 是 m 的整数倍。例如:6 | (23 - 5),23 和 5 对模 6 同余。
- 同余的符号记作:a ≡ \equiv ≡ b (mod m)。
8.2.5.2 一元线性同余方程
- 因为 a ≡ \equiv ≡ b (mod m) ,所以 ax - b 是 m 的整数倍,设倍数为 y ,那么 ax - b = my。
8.2.5.3 逆元
- 方程 ax ≡ \equiv ≡ b (mod m) 的一个解 x ,称 x 为 a 模 m 的逆。这样的 x 有很多。
int mod_reverse(int a, int m){
int x, y;
extend_gcd(a, m, x, y);
return (m + x % m) % m;
}
8.2.5.4 逆元与除法取模
- 设 b 的 逆元为 k;
- ( a b \frac{a}{b} ba) mod m = (( a b \frac{a}{b} ba) mod m) ((bk) mod m) = ( a b \frac{a}{b} babk) mod m = (ak) mod m;
8.2.5.5 逆元求解二元一次方程 ax + my = b
步骤 | 求解方程 ax + my = b 同余方程 ax ≡ \equiv ≡ b (mod m) | 例:求解 8x + 31y = 24 同余方程是 8x ≡ \equiv ≡ 24 (mod 31) a = 8,b = 24,m = 31 |
---|---|---|
1 | 有界的条件:gcd(a, m) 能整除 b | gcd(8, 31) = 1 能整除 24 |
2 | 求 ax
≡
\equiv
≡ 1 (mod m) 的逆元 a’ ,等价 于用扩展欧几里得算法求解 ax + my = 1 | 8x + 31y = 1 的一个解是 x = 4,y = -1即一个逆元是 a’ = 4 |
3 | 一个特解是 x = a’b | x = a’ b = 4 * 24 = 96 |
4 | 带入方程 ax + my = b,求解 y | 代入 8x + 31y = 24,得到 y = -24 |
8.2.6 素数
8.2.6.3 用埃式素数筛求素数数量
// 埃式素数筛求素数数量
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e7;
int prime[MAXN + 1];
int visited[MAXN + 1];
int e_sieve(int n) {
int k = 0;
for(int i = 0;i <= n;i ++) visited[i] = false;
for(int i = 2;i <= n;i ++)
if(!visoted[i]){
prime[k ++];
for(int j = 2 * i;j <= n;j += i) visited[j] = true;
}
return k;
}
// 优化后的埃式素数筛
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e7;
int prime[MAXN + 1];
int visited[MAXN + 1];
int e_sieve(int n) {
for(int i = 0;i <= n;i ++) visited[i] = false;
for(int i = 2;i * i <= n;i ++)
if(!visited[i])
for(int j = i * i;j <= n;j += i) visited[j] = true;
int k = 0;
for(int i = 2;i <= n;i ++)
if(!visited[i])
prime[k ++];
return k;
}