將一個正整數N分解成幾個正整數相加,能夠有多種分解方法,例如7=6+1,7=5+2,7=5+1+1,…。編程求出正整數N的全部整數分解式子。
輸入格式:
每一個輸入包含一個測試用例,即正整數N (0<N≤30)。
輸出格式:
按遞增順序輸出N的全部整數分解式子。遞增順序是指:對於兩個分解序列N1={n1,n2,⋯}和N2={m1,m2,⋯},若存在i使得n1=m1,⋯,ni=mi,可是ni+1<mi+1,則N1序列一定在N2序列以前輸出。每一個式子由小到大相加,式子間用分號隔開,且每輸出4個式子後換行。
輸入樣例:算法7輸出樣例1:編程
7=1+1+1+1+1+1+1;7=1+1+1+1+1+2;7=1+1+1+1+3;7=1+1+1+2+2 7=1+1+1+4;7=1+1+2+3;7=1+1+5;7=1+2+2+2 7=1+2+4;7=1+3+3;7=1+6;7=2+2+3 7=2+5;7=3+4;7=7
題目:基礎實驗2-2.5 整數分解爲若干項之和 (20分)性能
看到這題第一反應是分治。很像全排列問題,先寫出第一項,再寫出剩下的數的分解項。一塊兒來分析步驟。
首先假設咱們要求 $F(4)$, 即寫出 $n=4$ 的全部分解項。那麼咱們能夠先寫出第 1 位的分解項的全部可能。即 $k_1=1,2,3,4$。對於每一個 $k_1$,咱們只需求解它的子問題 $F(n-k_1)$ 便可。重複以上步驟,可得一棵結果樹:
測試
因爲題目中要求結果序列升序排列,因此咱們要去除重複狀況與降序狀況,分析後發現:spa
整理一下,可得:處於區間 $[0,k_{i-1}-1]\bigcup(\lfloor\frac n2\rfloor,n)$ 的狀況必須捨去,咱們只須要區間 $[k_{i-1},\lfloor\frac n2\rfloor]\bigcup\{n\}$ 即可。對於 $F=4$ 的狀況,咱們能夠看作是 $F(5)=1+F(4)$ ,可得 $k_{i-1}=1$,帶入剛纔推導出的公式得第 1 位的分解項的全部值爲 $i=1,2,4$。
對於上述全部i,問題都被分解成了:對應的 $k_i$ 與 $F(n-k_i)$ 。遞歸這個過程,獲得一棵以下的樹:
code
遞歸公式也不難寫出來:
$$Recursion:\begin{cases}n==0, (遞歸基)\\F(n)=k_i+F(n-k_i),\ (i\in[k_{i-1},\lfloor\frac n2\rfloor]\bigcup\{n\})\end{cases}$$ blog
相信看到這裏不少同窗已經能用DFS解出來了,估計核心代碼在六、7行左右。可是看到幾十層的遞歸棧······打擾了。筆者決定再推一下遞推式,畢竟性能快好多不是。
PS.筆者數學巨爛,下面的推導過程不是很容易懂,正在研究2.0版本...遞歸
下面根據遞歸式去推導遞推。 get
顯然,F(n)的分解式不會超過 $n$ 項,也就是第一次DFS到底時, $\underbrace{1+1+1+···+1}_{n個1}$這種狀況,設爲序列U。咱們從這種狀況開始反推。因爲公式爲 $F(n)=k_i+F(n-k_i)$, 將序列U末尾2位代入公式,得 : 數學
$$\begin{cases}k_{n-1}=1, \\n-k_{n-1}=n-1=1\Rightarrow n=2\end{cases}$$
此時再斷定有無處於區間 $[k_{n-1},\lfloor\frac n2\rfloor]\bigcup\{n\}$ 的其餘 $k_{n-1}$,有的話每次只須要處理比當前 $k_{n-1}$ 大 1 的狀況,即若是 $j=\lfloor\frac n2\rfloor-k_{n-1}>0$,進行j次循環分解,每次執行 $n-=k_i+1$ ,遇到逆序狀況則退出循環,最後存留的 $n$ 即爲最後一位的值。與遞歸相反,整個遞推過程當n=30時天然結束遞歸了。
遞歸的代碼網上都是,筆者就不傳了,這邊給出非遞歸的代碼,應該是性能一致的狀況下代碼行數最少的版本了吧。看到其餘博主好像基本上都用goto來實現非遞歸的,未免也太...
#include <stdio.h> int main() { int n = 0, a[31] = {0}; scanf("%d", &n); for(int i=0; i<n; i++){ a[i] = 1; } int pos = n-1, temp = 0, cnt = 0; while(pos>=0){ printf("%d=", n); for(int i=0; i<=pos; i++){ if(i==pos){ printf("%d", a[i]); }else{ printf("%d+",a[i]); } } if(++cnt%4 == 0){ printf("\n"); cnt=0; }else{ if(pos!=0) printf(";"); } if(pos==0) break; int sum = a[pos] + a[pos-1]; a[pos--] = 0; temp = a[pos] + 1; int j = (double)sum/2; int k = j - a[pos]; while(k-->0){ if(sum-temp < temp) break; sum -= temp; a[pos++] = temp; } a[pos] = sum; } return 0; }
涉及到複雜一些的遞推遞歸DP回溯搜索的算法,作的時候很快,但給別人講我仍是說不明白...啊我好菜...