【数论】——约数
文章目录
定义
对一个整数 n 除以整数 d,余数 r 为 0,则称 d 是 n 的一个约数,n 为 d 的一个倍数,记为:
d
∣
n
d|n
d∣n
因数分解
试除法
- 求 x 的因数,即从 1 开始向后扫描,如果
x%i == 0
,则将 i 与 x/i 记为 x 的一对因数。 - 优化:
由于合数的因数对关于 x \sqrt x x 对称,扫描到 x 会把每一对因数算 2 次,因此只需要扫描 [1, x \sqrt x x] 即可得到 x 的所有因数。
试除法的推论
一个整数 x 的约数个数最多为:
2
x
2\sqrt x
2x
- 代码
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] 中每个数的倍数个数表示为:
n d \frac{n}{d} dn
则所有数的倍数个数为:
c n t = N 2 + N 3 + . . . + N N − 1 + N N cnt=\frac{N}{2}+\frac{N}{3}+...+\frac{N}{N-1}+\frac{N}{N} cnt=2N+3N+...+N−1N+NN
c n t = N ( 1 2 + 1 3 + . . . + 1 N − 1 + 1 N ) cnt = N\left(\frac{1}{2}+\frac{1}{3}+...+\frac{1}{N-1}+\frac{1}{N} \right) cnt=N(21+31+...+N−11+N1)
此式为 调和级数:
c n t = ln N + C cnt = \ln N+{C} cnt=lnN+C
因此复杂度为:O( n ⋅ ( ln n ) n\cdot \left( \ln n \right) n⋅(lnn))。
倍数法的推论
对于区间 [1,n] 中所有数的约数个数总和为:
N
log
N
N\log N
NlogN
- 代码
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 = p 1 c 1 + p 2 c 2 + . . . + p m c m N=\; p_{1}^{c_{1}}+\; p_{2}^{c_{2}}+...+\; p_{m}^{c_{m}} N=p1c1+p2c2+...+pmcm
其中, c i c_{i} ci 是正整数, p i p_{i} pi 是质数,且随着下标严格增大。
算术基本定理的推论
- N 所有因数的个数
c o u n t O f F a c t e r = ( c 1 + 1 ) ∗ ( c 2 + 2 ) ∗ . . ∗ ( c m + 1 ) countOfFacter = (c_{1}+1)*(c_{2}+2)*..*(c_{m}+1) countOfFacter=(c1+1)∗(c2+2)∗..∗(cm+1)
- 推导
每一个 p i c i \; p_{i}^{c_{i}} pici 都可以根据算术基本定理继续分解,因此可以构造出 ( c 1 + 1 ) (c_{1}+1) (c1+1) 个因数,再由乘法原理,最终可以得到总共的因数个数为上式。 - 代码
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 所有因数的和
s u m O f F a c t o r = ( p 1 0 + p 1 1 + p 1 2 + . . . + p 1 c 1 ) ∗ ( p 2 0 + p 2 1 + p 2 2 + . . . + p 2 c 2 ) ∗ . . . ∗ ( p m 0 + p m 1 + p m 2 + . . . + p m c m ) sumOfFactor = (p_{1}^0+p_{1}^1+\;p_{1}^2+...+\;p_{1}^{c_{1}})*(p_{2}^0+p_{2}^1+\;p_{2}^2+...+\;p_{2}^{c_{2}})*...*(p_{m}^0+p_{m}^1+\;p_{m}^2+...+\;p_{m}^{c_{m}}) sumOfFactor=(p10+p11+p12+...+p1c1)∗(p20+p21+p22+...+p2c2)∗...∗(pm0+pm1+pm2+...+pmcm)
- 推导
1)中说明了每一个 p i c i \; p_{i}^{c_{i}} pici 可以构造出 ( c 1 + 1 ) (c_{1}+1) (c1+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,有:(证明为构造函数法)
g c d ( a , b ) ∗ l c m ( a , b ) = a ∗ b gcd(a,b) * lcm(a,b) = a*b gcd(a,b)∗lcm(a,b)=a∗b
欧几里得算法(辗转相除法)
- 对于任意自然数 a 与非零自然数 b:(证明为分 a>b,a≤b 讨论)
g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a,b) = gcd(b,a\;mod\;b) gcd(a,b)=gcd(b,amodb) - 代码
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;
}