整數拆分的兩種解法

前幾天在算法書上看到一個整數拆分的題目,覺得挺有意思,記錄如下:

 

題目一:給定一個整數n,輸出這個整數拆分的可能總數

例如:n==6有

6

5+1

4+2    4+1+1

3+3    3+2+1     3+1+1+1

2+2+2     2+2+1+1        2+1+1+1+1

1+1+1+1+1+1

共11種分解方法,所以輸出應該爲11。

分析一

拆分按照因子從大到小排列,每一次拆分都可視爲問題規模的減少,所以可以使用遞歸解決。

設q(n,m)爲整數n使用不大於m的整數進行拆分的所有情況總數,因此有

1)當n==m時

可以分爲兩種情況,一個是使用n本身,只有一種情況。二個是使用不大於n-1的整數進行拆分。

所以此時q(n,m)=1+q(n,n-1);

2)當n<m時

使用比n大的數進行拆分沒有意義,所以此時q(n,m)=q(n,n)

3)當n>m時

這時候有兩種情況,一個是使用m對n進行拆分,一個是使用小於m的數對n進行拆分

對於第一個,使用m對n進行拆分,所以拆分出來的情況最大的數是m,剩下的所有數加起來爲n-m,問題變成使用不大於m的整數對n-m進行拆分,所以此時爲q(n-m,m)

對於第二個,使用小於m的數對n進行拆分,表示爲q(n,m-1)

4)當n==1或者m==1時 只有一種情況,返回1。注意,這裏可能會漏掉m==1的情況,實際上這種情況是存在的。

所以可以使用遞歸函數編程如下:

  1. #include <stdio.h>  
  2. #include <algorithm>  
  3. using namespace std;  
  4.   
  5. int q(int n,int m){  
  6.     if(n==1||m==1){  
  7.         return 1;  
  8.     }  
  9.     if(n<m)  return q(n,n);  
  10.     if(n==m){  
  11.         return q(n,m-1)+1;  
  12.     }  
  13.     if(n>m)  
  14.         return q(n,m-1)+q(n-m,m);  
  15. }  
  16.   
  17. void main(){  
  18.     int n;  
  19.     while(scanf("%d",&n)!=EOF){  
  20.         printf("%d\n",q(n,n));  
  21.     }  
  22. }  


分析二

使用母函數法

整數分解用母函數可以這樣理解,分別用任意個1,2,3,4,……,n可以加起來可以表示成n的種數。又因爲當使用整數m對n進行分解時,所使用的次數不能多於n/m次,所以可以寫下母函數如下:

G(x)=(1+x^1+x^2+……+x^n)*(1+x^2+x^4+……+x^((n/2)*2))*……

          *(1+x^m+x^(2*m)+x^(3*m)+……+x^((n/m)*m))*……*(1+x^n)

在程序中使用set數組表示每一輪乘法後得到係數,c數組表示到現在爲止乘法得到的係數總和。最後算出結果後x^n對應的係數則爲可分解的可能數。代碼如下:


  1. #include<stdio.h>  
  2. const int num=1000;  
  3. int set[num];  
  4. int c[num];  
  5. void init(){  
  6.     for(int i=0; i<num; i++){  
  7.         c[i]=1;  
  8.         set[i]=0;  
  9.     }  
  10. }  
  11.   
  12. int main(){  
  13.     int n;  
  14.     int sum;  
  15.     init();  
  16.     while(scanf("%d",&n)!=EOF){  
  17.         sum=0;  
  18.         init();  
  19.         for(int i=2; i<=n; i++){  
  20.             for(int j=0; j<=n; j+=i){  
  21.                 for(int k=0; j+k<=n; k++){  
  22.                         set[k+j]+=c[k];   
  23.                 }  
  24.             }  
  25.             for(int x=0; x<=n; x++){  
  26.                 c[x]=set[x];  
  27.                 set[x]=0;  
  28.             }  
  29.         }  
  30.         printf("%d\n",c[n]);  
  31.     }  
  32.     return 0;  
  33. }  



題目二:給定一個整數n,輸出這個整數拆分的可能形式(即輸出全部情況)

使用遞歸情況(輸出實在麻煩。。弄了很久(>﹏<))

整個輸出類似於一顆樹,以分解6爲例,過程如下圖


 代碼如下:

  1. #include <stdio.h>  
  2.   
  3. //使用一個數組記錄在遞歸過程中產生的前面需要重複輸出的值  
  4. int set[100];  
  5. //用於在遞歸過程中判斷是否遞歸到最深處,輸出回車  
  6. int k;  
  7.   
  8. //此函數表示使用不大於m的整數對n進行拆分的情況,i用於表示set數組已經存在的記錄數長度  
  9. void q(int n,int m,int i){  
  10.     if(n==k&&n!=m){   
  11.         //此時遞歸棧已經退回到某一分支的最上層,輸出回車  
  12.         //並重置計數器i爲0  
  13.         printf("\n");  
  14.         i=0;  
  15.     }  
  16.     if(n==1){  
  17.         //當n爲1,意味者着只能表示1  
  18.         printf("1 ");  
  19.         return;  
  20.     }  
  21.     else if(m==1){  
  22.         //當m爲1,意味着要輸出n個m相加  
  23.         for(int i=0; i<n-1; i++)  
  24.             printf("1+");  
  25.         printf("1 ");  
  26.         return;  
  27.     }  
  28.     if(n<m) {  
  29.         q(n,n,i);  
  30.     }  
  31.     if(n==m){  
  32.         //當n等於m時,到達本次遞歸求和的一個葉子,此時需要輸出多一個空格,表示下一次輸出爲另一個葉子  
  33.         printf("%d ",n);  
  34.         //在遞歸輸出另一個葉子之前,將之前記錄的在葉子之上的數一併輸出,如上圖示過程1  
  35.         for(int j=0; j<i; j++)  
  36.             printf("%d+",set[j]);  
  37.         q(n,m-1,i);  
  38.           
  39.     }  
  40.     if(n>m){  
  41.         //如果n大於m,使用m作爲分解,則要首先輸出m+的形式  
  42.         printf("%d+",m);  
  43.         //記錄下作爲樹幹節點m的值並使i自增  
  44.         set[i++]=m;  
  45.         //遞歸輸出m+以後的分解  
  46.         q(n-m,m,i);  
  47.         //遞歸完畢後需要將數組記錄後退一個,回到上一個節點,如上圖示過程2  
  48.         i--;  
  49.         //執行另一個分支,在下一次遞歸之前輸出記錄的數據,如上圖示過程3  
  50.         for(int j=0; j<i; j++)  
  51.             printf("%d+",set[j]);  
  52.         //遞歸輸出另一分支情況  
  53.         q(n,m-1,i);  
  54.     }  
  55.       
  56.       
  57. }  
  58.   
  59. void main(){  
  60.     int n;  
  61.     while(scanf("%d",&n)!=EOF){  
  62.         if(n<=0){  
  63.             printf("Please input a positive interger.\n\n");  
  64.             continue;  
  65.         }  
  66.         k=n;  
發佈了1 篇原創文章 · 獲贊 8 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章