題目連接:http://bailian.openjudge.cn/practice/4117/html
總時間限制: 100ms 內存限制: 65536kB
描述
將正整數n 表示成一系列正整數之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
正整數n 的這種表示稱爲正整數n 的劃分。正整數n 的不一樣的劃分個數稱爲正整數n 的劃分數。ios
輸入
標準的輸入包含若干組測試數據。每組測試數據是一個整數N(0 < N <= 50)。
輸出
對於每組測試數據,輸出N的劃分數。
樣例輸入
5
樣例輸出
7
提示
5, 4+1, 3+2, 3+1+1, 2+2+1, 2+1+1+1, 1+1+1+1+1算法
注意對比題目:數組
(1)數的劃分(把n分爲k份,有點相似於放蘋果,但放蘋果這道題目裏面盤子能夠爲空,它不容許爲空。)ide
(2)複雜的整數劃分 測試
算法(一)遞歸法 spa
把一個正整數n寫成以下形式: n=m1+m2+...+mi; (其中mi爲正整數,而且1 <= mi <= n),則{m1,m2,...,mi}爲n的一個劃分。若是{m1,m2,...,mi}中的最大值不超過m,即max(m1,m2,...,mi)<=m,則稱它屬於n的一個m劃分。這裏咱們記n的m劃分的個數爲f(n,m);
例如當n=4時,他有5個劃分,{4},{3,1},{2,2},{2,1,1},{1,1,1,1};注意4=1+3 和 4=3+1被認爲是同一個劃分。.net
根據n和m的關係,考慮如下幾種狀況:
(1)當n=1時,不論m的值爲多少(m>0),只有一種劃分即{1};
(2) 當m=1時,不論n的值爲多少,只有一種劃分即n個1,{1,1,1,...,1};
(3) 當n=m時,根據劃分中是否包含n,能夠分爲兩種狀況:
(a). 劃分中包含n的狀況,只有一個即{n};
(b). 劃分中不包含n的狀況,這時劃分中最大的數字也必定比n小,即n的全部(n-1)劃分。所以 f(n,n) =1 + f(n,n-1);
(4) 當n<m時,因爲劃分中不可能出現負數,所以就至關於f(n,n);
(5) 但n>m時,根據劃分中是否包含最大值m,能夠分爲兩種狀況:
(a). 劃分中包含m的狀況,即{m, {x1,x2,...xi}}, 其中{x1,x2,... xi} 的和爲n-m,可能再次出現m,所以是(n-m)的m劃分,所以這種劃分個數爲f(n-m, m);
(b). 劃分中不包含m的狀況,則劃分中全部值都比m小,即n的(m-1)劃分,個數爲f(n,m-1);所以 f(n, m) = f(n-m, m)+f(n,m-1);code
綜合以上狀況,咱們能夠看出,上面的結論具備遞歸定義特徵,其中(1)和(2)屬於迴歸條件,(3)和(4)屬於特殊狀況,將會轉換爲狀況(5)。而狀況(5)爲通用狀況,屬於遞推的方法,其本質主要是經過減少m以達到迴歸條件,從而解決問題。其遞推表達式以下:
f(n, m) =1; (n=1 or m=1)
=f(n, n); (n<m)
=1+ f(n, m-1); (n=m)
=f(n-m,m)+f(n,m-1); (n>m)htm
1 #include <stdio.h> 2 long long GetPartitionCount(int n,int max) 3 { 4 if(n==1||max==1) return 1; 5 else if(n<max) return GetPartitionCount(n,n); 6 else if(n==max) return 1+GetPartitionCount(n,max-1); 7 else return GetPartitionCount(n,max-1)+GetPartitionCount(n-max,max); 8 } 9 int main(int argc, char *argv[]) 10 { 11 int n; 12 while(scanf("%d",&n)!=EOF) 13 { 14 printf("%lld\n",GetPartitionCount(n,n)); 15 } 16 return 0; 17 }
算法(二)動態規劃
這道題就是整數的劃分,其實定義好了狀態,就是簡單的動態規劃的遞推。
咱們定義dp[n][k]表示將n進行劃分,最大的數不超過k的方案有多少種,那麼咱們能夠獲得以下的遞推方案:
dp[n][k] = dp[n][k-1] + dp[n-k][k];
其中的dp[n][k-1]即是將n進行進行整數的劃分,最大的數不超過k-1的方案數;dp[n-k][k]表示拿出一個k後,剩下的數被不超過k的數的表示的方案數。
其中當k>n的時候,則和dp[n][n]的值相同,下面經過遞推的方式求出全部的解,而後對應的輸入n,輸出dp[n][n]就好了。
1 #include<iostream> 2 #include<cstring> 3 #include<cstdlib> 4 #include<cstdio> 5 using namespace std; 6 7 const int MAX = 122; 8 int dp[MAX][MAX]; 9 10 void Dynamic() 11 { 12 for(int i=1; i<MAX; i++) 13 { 14 dp[i][1] = dp[1][i] = dp[0][i] = 1; 15 } 16 for(int i=2; i<MAX; i++) 17 { 18 for(int j=2; j<MAX; j++) 19 { 20 if(j<=i) dp[i][j] = dp[i][j-1] + dp[i-j][j]; 21 else dp[i][j] = dp[i][i]; 22 } 23 } 24 } 25 int main() 26 { 27 int n; 28 Dynamic(); 29 while(scanf("%d",&n)!=EOF) 30 { 31 printf("%d\n",dp[n][n]); 32 } 33 return 0; 34 }
這道題的動規思路,下面的論述比較清晰一些:
整數劃分問題
數 n 的劃分是將 n 表示成多個正整數之和的形式,劃分能夠分爲兩種狀況:
第一種狀況:劃分的多個正整數中,正整數的數量是任意的。
這又能夠分爲劃分的正整數中,正整數能夠相同與不一樣兩類
1. 劃分的多個正整數能夠相同, 遞推方程能夠表示爲:
(1) dp[n][m]= dp[n][m-1]+ dp[n-m][m]
分析dp[n][m]表示整數 n 的劃分中,每一個數不大於 m 的劃分數。則劃分數能夠分爲如下兩種狀況:
a. 劃分中每一個數都小於 m, 至關於每一個數不大於 m- 1, 故劃分數爲 dp[n][m-1].
b. 劃分中有至少一個數爲 m. 那就在 n中減去 m , 剩下的就至關於把 n-m 進行劃分, 故劃分數爲 dp[n-m][m];
(2) dp[n][m]= dp[n][m+1]+ dp[n-m][m]
其中dp[n][m]表示整數 n 的劃分中,每一個數不小於 m 的劃分數。同理可證實該式。
2. 劃分的多個正整數互不相同,遞推方程能夠表示爲:
(1) dp[n][m]= dp[n][m-1]+ dp[n-m][m-1]
分析: dp[n][m]表示整數 n 的劃分中,每一個數不大於 m 的劃分數。一樣劃分狀況分爲如下兩種狀況:
a. 劃分中每一個數都小於 m, 至關於每一個數不大於 m- 1,劃分數爲 dp[n][m-1].
b. 劃分中有一個數爲 m. 在 n 中減去 m, 剩下至關對n- m 進行劃分,而且每個數不大於 m- 1,故劃分數爲 dp[n-m][m-1]
(2) dp[n][m]= dp[n][m+1]+ dp[n-m][m]
其中dp[n][m]表示整數 n 的劃分中,每一個數不小於 m 的劃分數。
第二種狀況:劃分的多個正整數中,正整數的數量是固定的
把一個整數 n 無序劃分紅 k 份互不相同的正整數之和的方法總數。 方程爲: dp[n][k]= dp[n-k][k]+ dp[n-1][k-1];
證實方法參考: http://www.mydrs.org/program/html/0369.htm
另外一種理解,總方法能夠分爲兩類:
第一類: n 份中不包含 1 的分法,爲保證每份都 >= 2,能夠先拿出 k 個 1 分
到每一份,而後再把剩下的 n- k 分紅 k 份便可,分法有: dp[n-k][k]
第二類: n 份中至少有一份爲 1 的分法,能夠先那出一個 1 做爲單獨的1份,剩
下的 n- 1 再分紅 k- 1 份便可,分法有:dp[n-1][k-1]
相似問題:
M個小球裝N個盒子,或者蘋果裝盤問題。好比:把M個球放到N個盒子,容許有空的盒子(不放球),有多少种放法?這些都屬於典型的DP問題。
用F(m,n)表示有多少种放法:
若是m=0 或者 m=1 , F = 1
若是n=0 或者 n=1 , F =1
既F(0,0) = F(0,1) = F(1,0) = F(1,1) = 1
不然 F = F(m-n,n) + F(m,n-1)這就是DP的解空間遞歸解
關於整數的質因子和分解
【問題描述】
歌德巴赫猜測說任何一個不小於6的偶數均可以分解爲兩個奇素數之和。對此問題擴展,若是一個整數可以表示成兩個或多個素數之和,則獲得一個素數和分解式。對於一個給定的整數,輸出全部這種素數和分解式。注意,對於同構的分解只輸出一次(好比5只有一個分解2 + 3,而3 + 2是2 + 3的同構分解式)。
例如,對於整數8,能夠做爲以下三種分解:
(1) 8 = 2 + 2 + 2 + 2
(2) 8 = 2 + 3 + 3
(3) 8 = 3 + 5
【算法分析】
因爲要將指定整數N分解爲素數之和,則首先須要計算出該整數N內的全部素數,而後遞歸求解全部素數和分解便可。
原做者的C++代碼:(感受其實就是回溯的思路)
1 #include <iostream> 2 #include <vector> 3 #include <iterator> 4 #include <cmath> 5 using namespace std; 6 7 // 計算num內的全部素數(不包括num) 8 void CalcPrimes(int num, vector<int> &primes) 9 { 10 primes.clear(); 11 if (num <= 2) 12 return; 13 14 primes.push_back(2); 15 for (int i = 3; i < num; i += 2) 16 { 17 int root = int(sqrt(i)); 18 int j = 2; 19 for (j = 2; j <= root; ++j) 20 { 21 if (i % j == 0) 22 break; 23 } 24 if (j > root) 25 primes.push_back(i); 26 } 27 } 28 29 // 輸出全部素數組合(遞歸實現) 30 int PrintCombinations(int num, const vector<int> &primes, int from, vector<int> &numbers) 31 { 32 if (num == 0) 33 { 34 cout << "Found: "; 35 copy(numbers.begin(), numbers.end(), ostream_iterator<int>(cout, " ")); 36 cout << '\n'; 37 return 1; 38 } 39 40 int count = 0; 41 42 // 從第from個素數搜索,從而避免輸出同構的多個組合 43 int primesNum = primes.size(); 44 for (int i = from; i < primesNum; ++i) 45 { 46 if (num < primes[i]) 47 break; 48 numbers.push_back(primes[i]); 49 count += PrintCombinations(num - primes[i], primes, i, numbers); 50 numbers.pop_back(); 51 } 52 53 return count; 54 } 55 56 // 計算num的全部素數和分解 57 int ExpandedGoldbach(int num) 58 { 59 if (num <= 3) 60 return 0; 61 62 vector<int> primes; 63 CalcPrimes(num, primes); 64 65 vector<int> numbers; 66 return PrintCombinations(num, primes, 0, numbers); 67 } 68 69 int main() 70 { 71 for (int i = 1; i <= 20; ++i) 72 { 73 cout << "When i = " << i << ":\n"; 74 int count = ExpandedGoldbach(i); 75 cout << "Total: " << count << "\n\n"; 76 } 77 }
C語言代碼以下:
1 #include<stdio.h> 2 #include<math.h> 3 #include<string.h> 4 5 #define maxNum 1000 6 int primeNum[maxNum]={0},ansArr[maxNum]={0}; 7 int indexForPrimeNum,indexForAnsArr; 8 9 int work(int num);//輸出num的全部素數和分解的方案並返回其總方案數 10 int CalcPrimes(int num);//計算num內的全部素數(不包括num),結果保存在primeNum[]. 11 12 int PrintAns(int num,int from); 13 //回溯法的思想:從primeNum[]的第from個元素開始選擇元素來累加構造num。 14 //構造結果放在ansArr[]中。尋找到一個構造方案後輸出該方案. 15 //最終返回總的方案數 16 17 int main(int argc, char *argv[]) 18 { 19 for (int i = 1; i <= 20; ++i) 20 { 21 printf("When i = %d:\n",i); 22 int count = work(i); 23 printf("Total: %d\n\n",count); 24 } 25 return 0; 26 } 27 //計算num內的全部素數(不包括num),結果保存在primeNum[]. 返回數組元素個數 28 int CalcPrimes(int num) 29 { 30 int i,j,root,k=0; 31 32 if(num<2) return k; 33 memset(primeNum,0,sizeof(primeNum)); 34 35 primeNum[k++]=2; 36 for(i=3;i<num;i++) 37 { 38 root=sqrt(i); 39 for(j=2;j<=root;j++) 40 { 41 if(i%j==0) break; 42 } 43 if(j>root) primeNum[k++]=i; 44 } 45 return k; 46 } 47 //調用PrintAns()輸出num的全部素數和分解的方案,返回其總方案數 48 int work(int num) 49 { 50 if(num<=3) return 0; 51 52 indexForPrimeNum=CalcPrimes(num); 53 54 memset(ansArr,0,sizeof(ansArr)); 55 indexForAnsArr=0; 56 return PrintAns(num,0); 57 } 58 59 //回溯法的思想:從primeNum[]的第from個元素開始選擇元素來累加構造num。 60 //構造結果放在ansArr[]中。尋找到一個構造方案後輸出該方案. 61 //最終返回總的方案數 62 int PrintAns(int num,int from) 63 { 64 int i; 65 if(num==0) 66 { 67 printf("Found: "); 68 for(i=0;i<indexForAnsArr;i++) printf(" %d",ansArr[i]); 69 printf("\n"); 70 return 1; 71 } 72 73 int totalCount = 0; 74 //從第from個素數搜索,從而避免輸出同構的多個組合 75 for(i=from;i<indexForPrimeNum;i++) 76 { 77 if(num<primeNum[i]) break; 78 ansArr[indexForAnsArr++]=primeNum[i]; 79 totalCount+=PrintAns(num-primeNum[i],i); 80 indexForAnsArr--; 81 } 82 return totalCount; 83 }
參考:
http://www.cppblog.com/superKiki/archive/2010/05/27/116506.html
http://blog.csdn.net/geniusluzh/article/details/8118683