\quad 取个例子,对于12而言,其约数有1,2,3,4,6,126个不同的约数。怎么快速的求出一个数的约数数目有多少个呢?可以对这个数进行质因子分解,分解成 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; }\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=n xn,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; }