分解质因数
单个数分解质因数
算法原理
采用试除法分解质因数即可
时间复杂度
\(O(\sqrt{n})\) 或 \(O(\frac{\sqrt{n}}{ln(\sqrt{n})})\)
阶乘分解质因数
给定整数 \(N\),试把阶乘 \(N!\) 分解质因数,按照算术基本定理的形式输出分解结果中的 \(p_i\) 和 \(c_i\) 即可。
5
2 3
3 1
5 1
一般方法分析
如果使用单个数分解质因数的方法,时间复杂度粗略估计为\(O(n \sqrt{n})\),对于n的1e6的数据范围,大概率会超时
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10;
int primes_cnt[N];
void divide(int n)
{
for (int i = 2; i <= n / i; ++ i)
{
if (n % i == 0)
{
int cnt = 0;
while (n % i == 0)
{
++ cnt;
n /= i;
}
primes_cnt[i] += cnt;
}
}
if (n > 1) ++ primes_cnt[n];
}
int main()
{
int n;
cin >> n;
for (int i = 2; i <= n; ++ i) divide(i);
for (int i = 2; i <= n; ++ i)
if (primes_cnt[i] > 0) cout << i << ' ' << primes_cnt[i] << endl;
return 0;
}
如果从优化的角度思考,如果能将divide中求cnt的过程进行预处理,快速求出n中质因子i的个数,复杂度会降低一些。
但实际考虑到预处理\(log_in\)的时间复杂度为\(O(n)\),并且由于n的变化,每次都需要预处理,这样预处理就没有什么意义了。
正解分析
在一般方法中,我们的思考方向是对于每个数都进行一次分解质因数
如果换种思考方向就是对于每一个质数,计算在阶乘中总共有多少个
算法原理
首先说明,上述的新的思考方向理论上是正确的,其可行性体现在以下的性质中
- 性质1:\(n!\) 中 质因子 \(p\) 的个数为 \(\lfloor \frac{n}{p} \rfloor \ + \ \lfloor \frac{n}{p^2} \rfloor \ + \ ... \ + \ \lfloor \frac{n}{p^k} \rfloor\).(该性质的证明位于 质因数分解计算组合数)
使用该公式,对于每个质数而言,需要从\(\lfloor \frac{n}{p} \rfloor\) 到 \(\lfloor \frac{n}{p^k} \rfloor (p^k \leq n , p^{k + 1} > n)\),粗略计算为\(log_pn\)
而1到a中的质数个数为\(\frac{n}{logn}\),所以粗略计算该算法的时间复杂度为\(O(n)\)
流程
- 首先使用线性筛筛取可能范围内的质数
- 利用上述公式分别计算每个质数的个数
代码实现
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int primes[N], cnt, st[N];
void init(int n)
{
for (int i = 2; i <= n; ++ i)
{
if (!st[i]) primes[cnt ++] = i;
for (int j = 0; primes[j] * i <= n; ++ j)
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main()
{
int n;
cin >> n;
init(n);
for (int i = 0; i < cnt; ++ i)
{
int p = primes[i], cnt = 0;
for (int j = n; j; j /= p) cnt += j / p; // 使用while还需要定义临时变量,不如for简洁
cout << p << ' ' << cnt << endl; // 不需要判断cnt是否为0,因为至少会是1,因为n!中一定包含小于n的质数,这些质数的质因子就是他们自身,所以所有的质数的指数最小也会是1
}
return 0;
}