一、排除错误作法
错误作法I
直接算出\(N\)的阶乘,数据范围\(1e6\),阶乘太大\(long \ \ long\)装不下,就开高精度,然后再考虑质因子分解,这么麻烦就等着挂吧,而且,高精度后,也不知道咋能进行质数因子分解,反正我是不会...
错误作法II
把\(1~N\)中每一个数字都质因数分解,分别计算质因子个数,然后用桶计数,累加!
这样做的算法复杂度是多少呢?
\(O(N\sqrt{N})\),其中\(N=10^6\),那就是\(10^6 * 10^3=10^9\),这么干会\(TLE\)了!
二、经典算法分析
三、算法步骤
- 筛出\(1 \sim n\)的所有质数
- 枚举每个质因子\(x\),\(n!\)表示\(1 * 2 * 3... * n\),从\(1\)到\(n\)中,求\(x\)的次数:
\[cnt(x)= \left \lfloor \frac{n}{x^1} \right \rfloor + \left \lfloor \frac{n}{x^2} \right \rfloor + \left \lfloor \frac{n}{x^3} \right \rfloor + ...
\]
(直到\(x\)的次方大于\(n\)停止)
四、时间复杂度 \(O(n)\)
质数个数定理:\(1 \sim n\)中有大约有 $ \frac{n}{ln(n)}$ 个质数。
有\(\frac{n}{ln(n)}\)个质数,每个质数求\(log_xn\)次,
总的计算次数=$$ \frac{n}{ln(n)} * log_xn <= \frac{n}{log_2n} * log_2n =n$$
使用数字\(2\)进行换底,将等式值扩大,最大是\(n\),因此时间复杂度是\(O(n)\)级别
五、实现代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
//欧拉筛
const int N = 1e6 + 10;
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n) {
memset(st, 0, sizeof st);
cnt = 0;
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;
get_primes(n);
for (int i = 0; i < cnt; i++) {
int p = primes[i];
int s = 0;
// 方法1(yxc大佬思维):
// 思路:由大到小+除法降维
// 优点:不用考虑乘法而导致的爆int上限
// 缺点:逆向思维,不好想
for (int j = n; j; j /= p) s += j / p;
// 方法2(普通人正常思维)
// 思路:由小到大+累记乘方
// 优点:正向思维
// 缺点:因为存在极限的乘法,可能爆int,需要开LL变量
// for (LL j = p; j <= n; j *= p) s += n / j;
// 方法3(错误作法)
// 因为最后 j*=primes[i]会造成j爆int,变成负数,导致s变小)
// for (int j = p; j <= n; j *= p) s += n / j;
printf("%d %d\n", p, s);
}
return 0;
}