对于两个数 a 和 b,gcd(a,b)×lcm(a,b)=a×b。也就是最大公约数乘以最小公倍数等于两个数的乘积。
因此,一般先求最大公约数,然后用这个性质求最小公倍数。
欧几里得算法
欧几里得算法也称辗转相除法,可以求出两个整数的最大公约数。
算法流程:
设 a>b:
如果 b 是 a 的约数,那么 b 就是两者的最大公约数;
如果 b 不是 a 的约数,那么 gcd(a,b)=gcd(b,a%b)。
因为 a mod b 会不断减小,因此可以用递归进行求解。
代码实现:
longlonggcd(longlong a, longlong b){
if(!b) return a; // 如果 b 等于 0,说明 a 就是最大公约数returngcd(b, a % b);
}
时间复杂度:
求 gcd(a,b) 会遇到两种情况:
a<b,则 gcd(a,b)=gcd(b,a)
a>b,则 gcd(a,b)=gcd(b,a%b)
第二种情况会让 a 至少折半,因此最多执行 logn 次。第一种情况不会多于第二种,因此时间复杂度为 O(logn)。
证明
证明 gcd(a,b)=gcd(b,a%b),思路:先证左边等于右边,再证右边等于左边。
设 a>b,a mod b = a − k * b,其中 k=a/b,为整数:若 d 是 (a,b) 的公约数,则 d∣a 且 d∣b,于是 d∣(a−kb),则 d∣(a%b);因此 d 也是 (b,a%b) 的公约数;若 d 是 (b,a%b) 的公约数,则 d∣b 且 d∣(a−kb),于是 d∣(a−kb+kb)=d∣(a);因此 d 也是 (a,b) 的公约数;
#include<iostream>usingnamespace std;
#define int long longsignedmain(){
int n;
cin >> n;
int sum = 0;
for(int i = 1; i <= n/2; i++) {
sum += (n/i);
}
sum += n - n/2;
cout << sum << endl;
return0;
}
intphi(int n){
int ret = n;
for(int i = 2; i <= n / i; i++) {
if(n % i == 0) {
ret = ret / i * (i - 1); // 先除后乘,保证不会溢出while(n % i == 0) n /= i;
}
}
// 别忘记判断最后一个数if(n > 1) ret = ret / n * (n - 1);
return ret;
}
时间复杂度:枚举到 sqrt(N ),因此时间复杂度为 O(sqrt(N ))。
欧拉筛打表欧拉函数
问题背景:需要知道 [1,n] 中,每一个数的欧拉函数。
代码实现
constint N = 1e6 + 10;
int n;
bool st[N];
int p[N], cnt;
int phi[N];
voidget_phi(){
phi[1] = 1;
for(int i = 2; i <= n; i++) {
if(!st[i]) {
p[cnt++] = i;
phi[i] = i - 1;
}
for(int j = 1; 1LL * i * p[j] <= n; j++) {
int x = i * p[j]; // 当前要筛的数
st[x] = true;
if(i % p[j] == 0) {
phi[x] = p[j] * phi[i];
break;
} else {
phi[x] = (p[j] - 1) * phi[i];
}
}
}
}
时间复杂度:与线性筛时间复杂度一致,为 O(n)。
7. 费马小定理
同余式
如果两个整数 a,b 模 m 的余数相同,则称 a, b 模 m 同余,记作:a ≡ b(mod m)。
#include<iostream>usingnamespace std;
#define int long longint a, m;
string s;
intget_phi(int m){
int ret = m;
for(int i = 2; i <= m/i; i++) {
if(m % i == 0) {
ret = ret / i * (i - 1);
while(m % i == 0) m /= i;
}
}
if(m > 1) ret = ret / m * (m - 1);
return ret;
}
intget_b(string& s, int p){
int t = 0;
bool flag = false;
for(int i = 0; i < s.size(); i++) {
t = t * 10 + s[i] - '0';
if(t >= p) {
flag = true;
t %= p;
}
}
if(flag) t += p;
return t;
}
intqpow(int a, int b, int p){
int ret = 1;
while(b) {
if(b & 1) ret = (ret * a) % p;
a = a % p;
a = a * a % p;
b >>= 1;
}
return ret;
}
signedmain(){
cin >> a >> m >> s;
int phi = get_phi(m);
int b = get_b(s, phi);
cout << qpow(a,b,m);
return0;
}
#include<iostream>#include<cmath>usingnamespace std;
intgcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
intmain(){
int n;
cin >> n;
int ret;
cin >> ret;
ret = abs(ret);
for(int i = 2; i <= n; i++) {
int x;
cin >> x;
ret = gcd(ret, abs(x));
}
cout <<ret << endl;
return0;
}
乘法逆元一般用于求 a/b (modp) 的值。先算出 b 在模 p 意义下的乘法逆元 b^−1,然后计算 a×b^−1(modp) 的值,就可以将除法转化为乘法。
下面总结一下求逆元的各种方法。
方法一:费马小定理 + 快速幂
问题:求 a 在模 m 意义下的逆元。
前提条件:a,m 互质,且 m 是质数。
方法:求出 a^(m−2),就是 a 在模 m 意义下的逆元。
时间复杂度:O(logm)。
代码实现
// 必须要保证 a,m 互质,且 m 为质数。LL qpow(LL a, LL b, LL m){
LL ret = 1;
while(b) {
if(b & 1) ret = ret * a % m;
b >>= 1;
a = a * a % m;
}
return ret;
}
intmain(){
LL x, m;
cin >> x >> m;
cout << qpow(x, m - 2, m) << endl;
return0;
}
方法二:扩欧算法求逆元
问题:求 a 在模 m 意义下的逆元。
前提条件:a,m 互质。
方法:解同余方程 ax≡1(modm)。
时间复杂度:O(logm)。
代码实现
LL exgcd(LL a, LL b, LL& x, LL& y){
if(b == 0) {
x = 1, y = 0;
return a;
}
LL x1, y1, d;
d = exgcd(b, a % b, x1, y1);
x = y1, y = x1 - a / b * y1;
return d;
}
intmain(){
LL a, m;
cin >> a >> m;
LL x, y, d;
d = exgcd(a, m, x, y);
if(d == 1) cout << (x % m + m) % m << endl;
return0;
}
方法三:递推法
问题:求 1∼n 中所有的数在模 p 意义下的逆元。
前提条件:1∼n 中所有的数都与 p 互质。
方法:线性递推:
当 i=1:inv[1]=1
当 i>1:inv[i]=p−(p/i)×inv[pmodi]modp
时间复杂度:O(n)。
代码实现
LL n, p;
LL inv[N];
voidget_inv(){
inv[1] = 1;
for(int i = 2; i <= n; i++) {
inv[i] = p - p / i * inv[p % i] % p;
}
}
#include<iostream>usingnamespace std;
typedeflonglong LL;
constint N = 1e5 + 10;
int n;
LL m[N], r[N];
LL qmul(LL a, LL b, LL p){
LL sum = 0;
while(b) {
if(b & 1) sum = (sum + a) % p;
b >>= 1;
a = (a + a) % p;
}
return sum;
}
LL exgcd(LL a, LL b, LL& x, LL& y){
if(b == 0) {
x = 1, y = 0;
return a;
}
LL x1, y1;
LL d = exgcd(b, a % b, x1, y1);
x = y1, y = x1 - a / b * y1;
return d;
}
LL excrt(){
LL M = 1, ret = 0;
for(int i = 1; i <= n; i++) {
LL a = M, b = m[i], c = r[i] - ret; // 防溢出:把 c 补成最小非负整数
c = (c % b + b) % b;
// ax + by = c
LL x, y, d;
d = exgcd(a, b, x, y);
if(c % d) return-1; // x -> ax + by = 1// x -> ax + by = c -> x * (c / d)
LL k1 = b / d;
x = qmul(x, c / d, k1);
x = (x % k1 + k1) % k1;
ret = ret + x * M;
M = k1 * M;
ret = (ret % M + M) % M;
}
return ret;
}
intmain(){
cin >> n;
for(int i = 1; i <= n; i++) cin >> m[i] >> r[i];
cout << excrt() << endl;
return0;
}