ACM数学基础知识
主要内容
- 同余
- 快速幂
- 快速乘
- 欧拉函数
- 欧拉定理
- 费马定理
- 乘法逆元
- 扩展欧几里得算法
同余的性质
快速幂
快速幂用于求解 ,时间复杂度
。
用二进制表示有
位,令
,则有:
于是:
其中:
typedef long long ll; ll quick_mod(ll a, ll n, ll m) { if(!n) return 1 % m; ll res = 1; while (n) { if(n & 1) { // 二进制最后一位为 1是奇数 res = (res * a) % m; } a = (a * a) % m; n >>= 1; // 右移一位就是整除 2 } return res; }
快速乘
快速幂用于求解 ,思想与快速幂一致,时间复杂度
。
用二进制表示:
其中 .
于是:
其中:
typedef long long ll; ll quick_mod(ll a, ll b, ll m) { ll res = 0; while (b) { if(b & 1) { // 二进制最后一位为 1是奇数 res = (res + a) % m; } a = (a + a) % m; b >>= 1; // 右移一位就是整除 2 } return res; }
质数
算术基本定理
算术基本定理,又称正整数唯一分解定理,即每一个正整数都可以被分解成若干质数的乘积,且分解的方式唯一。
,其中
且
是质数,
质数的判定 —— 试除法
bool is_prime(int n) { for(int i = 2; i <= n / i; ++i) { if(n % i == 0) return false; } return true; }
约数
求一个数的所有约数 —— 试除法
如果 ,那么
。令
,则
。总体时间复杂度
vector<int> get_divisors(int n) { vector<int> res; for(int i = 1; i <= n / i; ++i) { if(n % i == 0) { res.push_back(i); if(i != n / i) res.push_back(n / i); // 如果 i 是 n 的约数,那么 n / i 也是 n 的约数 } } sort(res.begin(), res.end()); return res; }
约数个数
一个大于 的正整数
,
则 的正约数的个数为
约数之和
一个大于 的正整数
,
则 的正约数的个数为
欧拉函数
定义
中于
互质的个数
例:
求法:
例:
证明:
容斥原理
中和
互质的个数
- 从
中去掉
的倍数。
- 加上所有
的倍数 (所有
的倍数被去掉了两次)
- 减去所有
的倍数
- 重复上述步骤
把公式 展开就是公式
用定义求欧拉函数
时间复杂度
// 用定义求欧拉函数 // O(sqrt(N)) #include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; while(n--) { int a; cin >> a; int res = a; for(int i = 2; i <= a / i; i++) { if(a % i == 0) { // res = res * (1 - 1 / i); res = res / i * (i - 1); while(a % i == 0) a /= i; } } if(a > 1) res = res / a * (a - 1); } }
筛法求欧拉函数
中每一个数的欧拉函数
用定义求的时间复杂度
#include <iostream> #include <algorithm> using namespace std; typedef long long ll; const int N = 1000000 + 10; int prime[N], cnt; int phi[N]; bool st[N]; ll get_eulers(int n) { for(int i = 2; i <= n; ++i) { if(!st[i]) { primes[cnt++] = i; phi[i] = i - 1; // 小于质数 p 的所有数都与 p 互质 } for(int j = 0; primes[j] <= n / i; ++j) { st[primes[j] * i] = 1; if(i % primes[j] == 0) { // pj 是 i 的质因子 // phi(pj * i) phi(i) // pj * i 的所有质因子与 i 的所有质因子相同 // phi(i) = i * (1 - 1 / p1) ... (1 - 1 / pk) // phi(pj * i) = pj * i * (1 - 1 / p1) ... (1 - 1 / pk) // phi(pj * i) = pj * phi(i) phi[primes[j] * i] = primes[j] * phi(i); break; } // i mod pj != 0 // phi(i) = i * (1 - 1 / p1) ... (1 - 1 / pk) // phi(pj * i) = pj * i * (1 - 1 / p1) ... (1 - 1 / pk) * (1 - 1 / pj) // phi(pj * i) = phi(i) * pj * (1 - 1 / pj) = phi(i) * (pj - 1) phi[primes[j] * i] = phi[i] * (primes[j] - 1) ` } } ll res = 0; // phi(1) = 1 for(int i = 1; i <= n; ++i) { res += phi[i]; } return res; } int main() { int n; cin >> n; cout << get_eulers(n) << endl; return 0; }
欧拉定理
若 与
互质,则
例:
证明:
中,所有与
互质的数为:
也和
互质,且互不相同。(若相同,设
,则
,则
,矛盾)
为
中所有与
互质的数,且互不相同。
也为
中所有与
互质的数 (模
),且互不相同。
两个集合都包含 个与
互质的数,只不过顺序不同,因此两个集合相等。
则有
由于
因此
即可得出
证毕
费马定理
若 为质数,
,则
费马定理是欧拉定理的简单推论
证明:
若 为质数,则
,根据欧拉定理即可得证。
乘法逆元
定义
对于某一个 ,能找到一个
,使得
,则
称为
模
的逆元,记作
(不是
的负一次方,是一个整数)。
逆元的性质
问题即转化为:
对于某一个 ,求一个
,使得
,其中
是质数。
逆元的求法
由费马定理:
因此,如果 是质数,则
模
的逆元是
。因此可以用快速幂求逆元。
注意
和
的不互质时不存在逆元
#include <iostream> #include <cstdio> using namespace std; typedef long long ll; // a ^ k % p int qmi(int a, int k, int p) { int res = 1; while(k) { if(k & 1) res = (ll)res * a % p; k >>= 1; a = (ll)a * a % p; } return res; } int main() { int n; scanf("%d", &n); while(n--) { int a, k, p; scanf("%d%d%d", &a, &k, &p); int res = qmi(a, p - 2, p); if(a % p) printf("%d\n", res); else printf("impossible\n"); } return 0; }
裴蜀定理
有一对正整数 ,那么一定存在非零整数
,使得
和
的最大公约数是
和
能凑出来的最小的正整数
假设 和
,使得
,那么
一定是
和
的最大公约数的倍数。因为
是
的倍数,
也是
的倍数,所以
,也一定是
的倍数。
证明
构造法
构造的方法就是扩展欧几里得算法
扩展欧几里得算法
欧几里得算法
#include <iostream> #include <cstdio> using namespace std; int gcd(int a, int b) { if(!b) { return a; } return gcd(b, a % b); } int main() { int n; scanf("%d", &n); while(n--) { int a, b; scanf("%d%d", &a, &b); } return 0; }
扩展欧几里得算法
由于 ,因此
的一组解为
。
#include <iostream> #include <cstdio> using namespace std; int exgcd(int a, int b, int &x, int &y) { if(!b) { x = 1, y = 0; return a; } int d = exgcd(b, a % b, y, x); y -= a / b * x; return d; } int main() { int n; scanf("%d", &n); while(n--) { int a, b; scanf("%d%d", &a, &b); exgcd(a, b, x, y); printf("%d %d\n", x, y); } return 0; }
不唯一
求解线性同余方程
#include <iostream> #include <cstdio> using namespace std; typedef long long ll; int exgcd(int a, int b, int &x, int &y) { if(!b) { x = 1, y = 0; return a; } int d = exgcd(b, a % b, y, x); y -= a / b * x; return d; } int main() { int n; scanf("%d", &n); while(n--) { int a, b, m; scanf("%d%d%d", &a, &b, &m); int x, y; int d = exgcd(a, m, x, y); if(b % d) printf("impossible\n"); else printf("%d\n", (ll)x * (b / d) % m); } return 0; }