AcWing 197. 阶乘分解


题目传送门

一、排除错误作法

错误作法I

直接算出\(N\)的阶乘,数据范围\(1e6\),阶乘太大\(long \ \ long\)装不下,就开高精度,然后再考虑质因子分解,这么麻烦就等着挂吧,而且,高精度后,也不知道咋能进行质数因子分解,反正我是不会...

错误作法II

\(1~N\)中每一个数字都质因数分解,分别计算质因子个数,然后用桶计数,累加!
这样做的算法复杂度是多少呢?
\(O(N\sqrt{N})\),其中\(N=10^6\),那就是\(10^6 * 10^3=10^9\),这么干会\(TLE\)了!

二、经典算法分析

三、算法步骤

  1. 筛出\(1 \sim n\)的所有质数
  2. 枚举每个质因子\(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 
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;
}