1. 程式人生 > 其它 >AcWing 197. 階乘分解

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 <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;
}