範例程式碼已於UVA
、瘋狂程設(CPE)
、ZeroJudge
上皆測試通過。
在 ZeroJudge
上只能使用 C++
,Python
會超時。
題意
在每個測試案例中,會給定一個正整數 N N N ,請計算 N N N 的所有唯一因數分解方法數,並列出所有的因數分解。
對於每個輸入的正整數 N N N ,輸出其唯一的因數分解方法數 F F F 。如果 F > 0 F>0 F > 0 ,則接下來的 F F F 行應包含這些因數分解。每個因數分解中的數字應按非遞減順序排列,並且所有因數分解應按字典順序排列。同一組合視為相同,例如 12 = 4 × 3 12 = 4 \times 3 12 = 4 × 3 和 12 = 3 × 4 12 = 3 \times 4 12 = 3 × 4 視為相同。
注意:因數分解中不應包含 1 1 1 ,且每個因數分解至少包含兩個數。
約束條件
輸入文件中最多包含 20 20 20 個測試案例。
2 ≤ N ≤ 2 ⋅ 1 0 6 2 \leq N \leq 2 \cdot 10^6 2 ≤ N ≤ 2 ⋅ 1 0 6
思路:回溯(Backtracking)
使用回溯的方式來生成所有的因數分解,定義一個回溯函數 d f s ( x ) \rm{dfs}(x) dfs ( x ) ,其中 x x x 是當前待分解的數,並維護一個 p a t h path p a t h 陣列來存儲當前的因數分解。
每次遞迴時,嘗試將 x x x 分解為兩個或多個數的乘積,並確保結果是非遞減的。
為了確保結果是 非遞減 的,我們可以在找尋下一個因數時,限制因數的範圍為 [ p r e , x ] [pre, x] [ p re , x ] ,其中 p r e pre p re 是 p a t h path p a t h 中的最後一個數字,若 p a t h path p a t h 為空,則 p r e = 2 pre = 2 p re = 2 。
若下一個因數 y y y 能整除 x x x ,則將 y y y 加入 p a t h path p a t h 中,並遞迴處理 x / y x/y x / y 。
當 x x x 被分解為 1 1 1 時,且 p a t h path p a t h 的長度大於 1 1 1 時,將 p a t h path p a t h 加入答案中。
最後,輸出答案即可。
複雜度分析
時間複雜度:O ( 2 N ) \mathcal{O}(2^{\sqrt{N}}) O ( 2 N ) ,取決於 N N N 的因數分解可能性,由於分數可以兩兩一對,因此最壞情況下有 2 ⋅ 2 N 2 \cdot 2^{\sqrt{N}} 2 ⋅ 2 N 種因數,時間複雜度為 O ( 2 N ) \mathcal{O}(2^{\sqrt{N}}) O ( 2 N ) 。
空間複雜度:O ( 2 N ) \mathcal{O}(2^{\sqrt{N}}) O ( 2 N )
時間複雜度取決於 N N N 的因數分解可能性,因此很難估計其複雜度,這裡只是給出一個最壞情況的估計。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ans = [] path = [] def dfs (x ): if x == 1 and len (path) > 1 : ans.append(path[:]) return pre = path[-1 ] if path else 2 for y in range (pre, x+1 ): if x % y == 0 : path.append(y) dfs(x//y) path.pop() while True : n = int (input ()) if n == 0 : break ans.clear() dfs(n) print (len (ans)) for a in ans: print (" " .join(map (str , a)))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <bits/stdc++.h> using namespace std;#define endl '\n' vector<vector<int >> ans; vector<int > path; void dfs (int x) { if (x == 1 && path.size () > 1 ) { ans.push_back (path); return ; } int pre = path.empty () ? 2 : path.back (); for (int y = pre; y <= x; y++) { if (x % y == 0 ) { path.push_back (y); dfs (x / y); path.pop_back (); } } } int main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); cout.tie (nullptr ); int n; while (cin >> n && n) { ans.clear (); dfs (n); cout << ans.size () << endl; for (auto a : ans) for (int i = 0 ; i < a.size (); i++) cout << a[i] << (i == a.size () - 1 ? endl : ' ' ); } return 0 ; }
寫在最後
Cover photo is remixed from @Ice maker , thanks for their work!
公式化的回溯問題,建議可以去閱讀靈神的 基礎算法精講 ,掌握了回溯的基本模板後,再去解題會更加得心應手。