7215:簡單的整數劃分問題

題目連接: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 }
View Code

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 }
View Code

 

 

參考:

http://www.cppblog.com/superKiki/archive/2010/05/27/116506.html

http://blog.csdn.net/geniusluzh/article/details/8118683

相關文章
相關標籤/搜索