一、基本定义
1. 定义
(1)设 a1,a2 是两个整数,如果 x | a1,x | a2,那么 x 就称为 a1 和 a2 的公约数,其中最大的称为 a1 和 a2 的最大公约数,记作(a1,a2).
(2)设 a1,a2 是两个整数,如果 a1 | x,a2 | x,那么 x 就称为 a1 和 a2 的公倍数,其中最小的称为 a1 和 a2 的最小公倍数,记作 [ a1,a2 ].
2. 性质
【性质1】对任意整数 m,m(a1, ··· ,ak) = (ma1, ··· ,mak),即整数成倍放大,最大公约数也放大相同倍数。
同样适用于最小公倍数。
【性质2】对任意整数 x,(a1,a2) = (a1,a2+a1x),即一个整数加上另一个整数的任意倍数,它们的最大公约数不变。注意,(a,0) = a.
不适用于最小公倍数
【性质3】(a1,a2,a3,···,ak) = ((a1,a2),a3,···,ak),以及一个推论 (a1,a2,a3,···,ak+r) = ((a1,···,ak),(ak+1,···,ak+r))。说明最大公约数运算具有某种“结合律”,这是计算多元最大公约数的主要手段。
同样适用于最小公倍数。
【性质4】[ a1,a2 ] (a1,a2) = a1·a2,即最大公约数×最小公倍数 = 原来两个数的乘积。
3. 算数基本定理
设 a > 1,那么必有 a = p1α1 p2α2 ··· psαs,其中 pj 是两两不相同的质数,αj 表示对应质数的幂次
二、试除法求约数
-
思路:试除法求一个数的所有约数,从小到大判断,如果当前数能整除目标数,说明这个数是它的一个约数。
-
例题
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
vector<int> get_divisors(int n){
vector<int> res;
//从小到大枚举n的所有约数的每一对里面比较小的那一个
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);
}
}
sort(res.begin(), res.end());//排序
return res;
}
int main(){
int n;
cin >> n;
while (n--){
int x;
cin >> x;
auto res = get_divisors(x);
for (auto t : res)
cout << t << ' ';
cout << endl;
}
return 0;
}
三、求一个数的约数个数
-
求个数公式:(α1 + 1)(α2 + 1) ··· (αk + 1).
-
在 int 范围内,约数最多的个数为1500个左右。
-
例题
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
int main(){
int n;
cin >> n;
unordered_map<int, int> primes;
while (n--){
int x;
cin >> x;
for (int i = 2; i <= x / i; i++){
while (x % i == 0)
{
x /= i;
primes[i]++;
}
}
if (x > 1) primes[x]++;
}
LL res = 1;
for (auto prime : primes)
res = res * (prime.second + 1) % mod;
cout << res << endl;
return 0;
}
四、求一个数的约数之和
-
求约数之和公式:(p10 + p11 + p12 + ··· + p1α1)×(···)×(pk0 + pk1 + pk2 + ··· + pkαk).
-
例题
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
int main(){
int n;
cin >> n;
unordered_map<int, int> primes;
while (n--){
int x;
cin >> x;
for (int i = 2; i <= x / i; i++){
while (x % i == 0)
{
x /= i;
primes[i]++;
}
}
if (x > 1) primes[x]++;
}
LL res = 1;
for (auto prime : primes){
int p = prime.first, a = prime.second;
LL t = 1;
while (a--){
t = (t * p + 1) % mod;
}
res = res * t % mod;
}
cout << res << endl;
return 0;
}
五、求最大公约数与最小公倍数
- 上文中【性质2】可以给出一个高效的求两数最大公约数的算法:每次让较大的数对较小数取模,可以缩小问题规模而保持最大公约数不变,然后重复(递归)这个步骤。递归边界使某数变成了0,而此时另一个数即为所求答案,于是得到如下代码:
int gcd(int a, int b){
if (b == 0) return a; //递归边界
else return gcd(b, a % b);
}
这种利用 两数相除(取模)求最大公约数 的方法叫做 辗转相除法 或 欧几里得算法,最坏情况下的时间复杂度为 O(log max(x,y)). 对于大多数情况,辗转相除法时间可以忽略不计。
经过压行后,可以得到单行辗转相除法,如下;
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
- 利用【性质4】,用两数之积除以它们的最大公约数可得最小公倍数,代码如下:
int lcm(int a, int b){
return a / gcd(a, b) * b; //要注意乘除的先后顺序,防止溢出
}
- 例题:给定 n 对正整数 a,b, 请你求出每对数的最大公约数和最小公倍数。
#include <iostream>
using namespace std;
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int lcm(int a, int b)
{
return a / gcd(a, b) * b;
}
int main()
{
int n;
scanf("%d", &n);
while (n--){
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", gcd(a, b));
printf("%d\n", lcm(a, b));
}
return 0;
}