【数论】——约数
定义
对一个整数 n 除以整数 d,余数 r 为 0,则称 d 是 n 的一个约数,n 为 d 的一个倍数,记为:
因数分解
试除法
- 求 x 的因数,即从 1 开始向后扫描,如果
x%i == 0,则将 i 与 x/i 记为 x 的一对因数。 - 优化: 由于合数的因数对关于 对称,扫描到 x 会把每一对因数算 2 次,因此只需要扫描 [1,] 即可得到 x 的所有因数。
试除法的推论
一个整数 x 的约数个数最多为:
- 代码
int factor[N], cnt = 0;//factor 存所有因数,cnt 表示个数
void get_factor(int x)
{
/*
这里建议写 i<=x/i
原因:
1)写 sqrt(x),要调用 <cmath> 的函数,太慢、
2)写 i*i<=x, 如果 i 过大会溢出。
*/
for (int i = 1; i <= i / n; i++) {if (x % i == 0) {factor[++cnt] = i;// 加入 i
if (i != x / i)// 如果 i 不是 √x,加入 x/i
factor[++cnt] = x / i;
}
}
}
倍数法求区间内数的所有因数
- 求 [1,n] 中所有数的因素,等价于对于 [1,n] 中每个数 d,求在 [1,n] 中所有以 d 为约数的数
- 复杂度分析 将 [1,n] 中每个数的倍数个数表示为:
则所有数的倍数个数为:
此式为 调和级数:
因此复杂度为:O()。
倍数法的推论
对于区间 [1,n] 中所有数的约数个数总和为:
- 代码
vector<int> factors[N];//factor[i] 存储数 i 的所有因数
void get_factors(int n)
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n / i; j++)// 防止重复,j 扫描到 n/i
factor[i * j].push_back(i);
// 打印:
for (int i = 1; i <= n; i++) {for (auto i : factor[i])
printf("%d", i);
puts("");
}
}
质因数分解
算术基本定理
- 任意正整数 x 大于 1,能被分解为有限个质数的乘积:
其中, 是正整数, 是质数,且随着下标严格增大。
算术基本定理的推论
- N 所有因数的个数
- 推导 每一个 都可以根据算术基本定理继续分解,因此可以构造出 个因数,再由乘法原理,最终可以得到总共的因数个数为上式。
- 代码
const int mod = 1e9 + 7;
unordered_map<int, int> primes; // 用来存(pi,ci)
int countOfFactor(int x)
{
for (int i = 2; i <= x / i; i++) {if (x % i == 0) {
int c = 0;
while (x % i == 0) {
c++;
x /= i;
}
primes[i] = c; // 记录(pi,ci)
}
}
// 如果 x 不为 1,则 x 为大于√x 的一个质因数
if (x > 1)
primes[x]++;
int res = 1;
for (auto i : primes)
res = (long long)res * (i.second + 1) % mod;
return res;
}
- N 所有因数的和
- 推导 1)中说明了每一个 可以构造出 个因数,构造每一个因数(括号内的部分),再由乘法原理即可求出所有因数的和。
- 代码
const int N = 1e9 + 7;
unordered_map<int, int> primes; // 用来存(pi,ci)
int countOfFactor(int x)
{
for (int i = 2; i <= x / i; i++) {if (x % i == 0) {
int c = 0;
while (x % i == 0) {
c++;
x /= i;
}
primes[i] = c;// 记录(pi,ci)
}
}
// 如果 x 不为 1,则 x 为大于√x 的一个质因数
if (x > 1)
primes[x]++;
int res = 1;
for (auto i : primes){
int p = i.first, c = i.second;
int tmp = 1;
// 计算每个括号
while (c--) {tmp = (long long)(tmp * p + 1) % N;
}
res = (long long)res * tmp % N;
}
return res;
}
最大公约数 / 最小公倍数(gcd & lcm)
定义
- 若存在自然数 d 同时是自然数 a,b 的约数,则称 d 为 a,b 的公约数,其中最大的满足以上条件的 d,称为最大公约数,记为
gcd(a,b)。 - 若存在自然数 m 同时是自然数 a,b 的倍数,则称 m 为 a,b 的公倍数,其中最小的满足以上条件的 d,称为最小公倍数,记为
lcm(a,b)。
乘积定理
- 对于任意的自然数 a,b,有:(证明为构造函数法)
欧几里得算法(辗转相除法)
- 对于任意自然数 a 与非零自然数 b:(证明为分 a>b,a≤b 讨论)
- 代码
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
- 顺便贴上扩展欧几里得(用于求解线性同余方程)
int gcd(int a, int b, int& x, int& y)
{
if (!b) {
x = 1, y = 0;
return a;
}
int d = gcd(b, a % b, x, y);
int t = x;
x = y;
y = t - y * (a / b);
return d;
}