一、一个数的约数个数有多少
\quad
取个例子,对于12而言,其约数有1,2,3,4,6,12
6个不同的约数。怎么快速的求出一个数的约数数目有多少个呢?可以对这个数进行质因子分解,分解成
N
=
p
1
α
1
p
2
α
2
⋯
p
n
α
n
N=p_1^{\alpha_1}p_2^{\alpha_2}\cdots p_n^{\alpha_n}
N=p1α1p2α2⋯pnαn,这样其约数个数为
(
α
1
+
1
)
∗
(
α
2
+
1
)
∗
(
α
n
+
1
)
(\alpha_1+1)*(\alpha_2+1)*(\alpha_n+1)
(α1+1)∗(α2+1)∗(αn+1)。理解起来也很简单,任意取一个数的质因子
p
i
p_i
pi取
0
α
i
0~\alpha_i
0 αi次,将这些质因子相乘得到的结果即为这个数的一个约数。比如
12
=
2
2
∗
3
1
12=2^2*3^1
12=22∗31:
- 取 0 个 2 和 0 个 3 , 得 约 数 1 0个2和0个3,得约数1 0个2和0个3,得约数1
- 取 1 个 2 和 0 个 3 , 得 约 数 2 1个2和0个3,得约数2 1个2和0个3,得约数2
- 取 2 个 2 和 0 个 3 , 得 约 数 4 2个2和0个3,得约数4 2个2和0个3,得约数4
- 取 0 个 2 和 1 个 3 , 得 约 数 3 0个2和1个3,得约数3 0个2和1个3,得约数3
- 取 1 个 2 和 1 个 3 , 得 约 数 6 1个2和1个3,得约数6 1个2和1个3,得约数6
- 取 2 个 2 和 1 个 3 , 得 约 数 12 2个2和1个3,得约数12 2个2和1个3,得约数12
这样就可以轻松求出这个数所有的约数。
#include <iostream>
#include <map>
using namespace std;
const int MOD = 1e9 + 7;
int main()
{
map<int, int> cnt; // 存放每个质因子出现次数
int num; cin >> num;
while(num -- )
{
int n; cin >> n;
for(int i = 2; i * i <= n; i ++ )
while(n % i == 0) n /= i, cnt[i] ++ ;
if(n > 1) cnt[n] ++ ;
}
long long res = 1;
for(auto &[p, a] : cnt) res = res * (a + 1) % MOD;
cout << res << endl;
return 0;
}
二、约数之和
\quad
继上面这个问题,再求解约数的和。对于
12
=
2
2
∗
3
1
12=2^2*3^1
12=22∗31来说,其约数之和为
2
0
∗
3
0
+
2
1
∗
3
0
+
2
2
∗
3
0
+
2
0
∗
3
1
+
2
1
∗
3
1
+
2
2
∗
3
1
=
(
2
0
+
2
1
+
2
2
)
∗
(
3
0
+
3
1
)
2^0*3^0+2^1*3^0+2^2*3^0+2^0*3^1+2^1*3^1+2^2*3^1=(2^0+2^1+2^2)*(3^0+3^1)
20∗30+21∗30+22∗30+20∗31+21∗31+22∗31=(20+21+22)∗(30+31)。借助这个例子,我们轻易可得:
对于任意正整数
N
=
p
1
α
1
p
2
α
2
⋯
p
n
α
n
N=p_1^{\alpha_1}p_2^{\alpha_2}\cdots p_n^{\alpha_n}
N=p1α1p2α2⋯pnαn,其约数之和为
(
p
1
0
+
⋯
+
p
1
α
1
−
1
)
∗
⋯
∗
(
p
n
0
+
⋯
+
p
n
α
n
−
1
)
(p_1^{0}+\cdots+p_1^{\alpha_1-1})*\cdots*(p_n^{0}+\cdots+p_n^{\alpha_n-1})
(p10+⋯+p1α1−1)∗⋯∗(pn0+⋯+pnαn−1)。给个例题:
#include <iostream>
#include <map>
using namespace std;
const int MOD = 1e9 + 7;
int main()
{
int n; cin >> n;
map<int, int> cnt;
while(n -- )
{
int t; cin >> t;
for(int i = 2; i * i <= t; i ++ )
while(t % i == 0) t /= i, cnt[i] ++ ;
if(t > 1) cnt[t] ++ ;
}
long long res = 1;
for(auto &[base, a] : cnt)
{
long long temp = 1, p = base;
for(int i = 0; i < a; i ++ ) temp = (temp + p) % MOD, p = base * p % MOD;
res = res * temp % MOD;
}
cout << res << endl;
return 0;
}
三、求1~n中每个数的约数的和 O ( n ) O(\sqrt{n}) O(n)
\quad
输入一个整数n,输出
1
,
2
,
.
.
.
,
n
1,2,...,n
1,2,...,n中每个数的约数的和。例如当n=3时,1有1个约数1;2有2个约数1,2;3有2个约数1,3。故而输出为5。下面给出时间复杂度为
O
(
n
)
O(\sqrt{n})
O(n)的做法。
\quad
首先我们可以得到约数和公式
y
=
n
1
+
n
2
+
⋯
+
n
n
y=\frac{n}{1} + \frac{n}{2} + \cdots + \frac{n}{n}
y=1n+2n+⋯+nn,直接这样做时间复杂度为
O
(
n
)
O(n)
O(n)。我们将公式看做一个函数:
y
=
∑
x
=
1
x
=
n
n
x
,
x
为
正
整
数
y=\sum_{x=1}^{x=n}{\frac{n}{x}},x为正整数
y=∑x=1x=nxn,x为正整数。这样相当于求
y
=
n
x
y=\frac{n}{x}
y=xn这个曲线与x轴围成的面积(只是相当于,毕竟这是离散点)。
y
=
n
x
y=\frac{n}{x}
y=xn这个曲线关于
y
=
x
y=x
y=x对称,交点在
x
\sqrt{x}
x处。故而其面积可以分为三部分计算:
- y = x , y 轴 和 y = n x y=\sqrt{x},y轴和y=\frac{n}{x} y=x,y轴和y=xn围成的部分 s 1 s1 s1
- y = x , x = x , x 轴 和 y 轴 y=\sqrt{x},x=\sqrt{x},x轴和y轴 y=x,x=x,x轴和y轴围成的部分 s 2 s2 s2
- x = x , x 轴 和 y = n x x = \sqrt{x},x轴和y=\frac{n}{x} x=x,x轴和y=xn围成的部分 s 3 s3 s3
\quad 由于 y = n x y=\frac{n}{x} y=xn关于 y = x y=x y=x对称,故而 s 1 = s 3 s1=s3 s1=s3。 s 1 + s 2 s1+s2 s1+s2的面积即为 ∑ x = 1 x = n n x , x 为 正 整 数 \sum_{x=1}^{x=\sqrt{n}}\frac{n}{x},x为正整数 ∑x=1x=nxn,x为正整数, s 2 s2 s2面积为 i n t ( x ) ∗ i n t ( x ) int(\sqrt{x})*int(\sqrt{x}) int(x)∗int(x)。令 t = i n t ( x ) t=int(\sqrt{x}) t=int(x)最终可得 y = s 1 + s 2 + s 3 = 2 ∗ ∑ x = 1 x = t n x − t ∗ t y=s1+s2+s3=2*\sum_{x=1}^{x=t}\frac{n}{x}-t*t y=s1+s2+s3=2∗∑x=1x=txn−t∗t。时间复杂度为 O ( n ) O(\sqrt{n}) O(n)。
long long solve(long long n) {
long long sum = 0;
int t = sqrt(n);
for (int i = 1; i <= t; i++) sum += n / i; // s1 + s2 面积
sum *= 2; // 2 * s1 + 2 * s2 面积
sum -= t * t; // 2 * s1 + s2 = s1 + s2 + s3
return sum;
}