前幾天在算法書上看到一個整數拆分的題目,覺得挺有意思,記錄如下:
題目一:給定一個整數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的情況,實際上這種情況是存在的。
所以可以使用遞歸函數編程如下:
- #include <stdio.h>
- #include <algorithm>
- using namespace std;
- int q(int n,int m){
- if(n==1||m==1){
- return 1;
- }
- if(n<m) return q(n,n);
- if(n==m){
- return q(n,m-1)+1;
- }
- if(n>m)
- return q(n,m-1)+q(n-m,m);
- }
- void main(){
- int n;
- while(scanf("%d",&n)!=EOF){
- printf("%d\n",q(n,n));
- }
- }
分析二
使用母函數法
整數分解用母函數可以這樣理解,分別用任意個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對應的係數則爲可分解的可能數。代碼如下:
- #include<stdio.h>
- const int num=1000;
- int set[num];
- int c[num];
- void init(){
- for(int i=0; i<num; i++){
- c[i]=1;
- set[i]=0;
- }
- }
- int main(){
- int n;
- int sum;
- init();
- while(scanf("%d",&n)!=EOF){
- sum=0;
- init();
- for(int i=2; i<=n; i++){
- for(int j=0; j<=n; j+=i){
- for(int k=0; j+k<=n; k++){
- set[k+j]+=c[k];
- }
- }
- for(int x=0; x<=n; x++){
- c[x]=set[x];
- set[x]=0;
- }
- }
- printf("%d\n",c[n]);
- }
- return 0;
- }
題目二:給定一個整數n,輸出這個整數拆分的可能形式(即輸出全部情況)
使用遞歸情況(輸出實在麻煩。。弄了很久(>﹏<))
整個輸出類似於一顆樹,以分解6爲例,過程如下圖
代碼如下:
- #include <stdio.h>
- //使用一個數組記錄在遞歸過程中產生的前面需要重複輸出的值
- int set[100];
- //用於在遞歸過程中判斷是否遞歸到最深處,輸出回車
- int k;
- //此函數表示使用不大於m的整數對n進行拆分的情況,i用於表示set數組已經存在的記錄數長度
- void q(int n,int m,int i){
- if(n==k&&n!=m){
- //此時遞歸棧已經退回到某一分支的最上層,輸出回車
- //並重置計數器i爲0
- printf("\n");
- i=0;
- }
- if(n==1){
- //當n爲1,意味者着只能表示1
- printf("1 ");
- return;
- }
- else if(m==1){
- //當m爲1,意味着要輸出n個m相加
- for(int i=0; i<n-1; i++)
- printf("1+");
- printf("1 ");
- return;
- }
- if(n<m) {
- q(n,n,i);
- }
- if(n==m){
- //當n等於m時,到達本次遞歸求和的一個葉子,此時需要輸出多一個空格,表示下一次輸出爲另一個葉子
- printf("%d ",n);
- //在遞歸輸出另一個葉子之前,將之前記錄的在葉子之上的數一併輸出,如上圖示過程1
- for(int j=0; j<i; j++)
- printf("%d+",set[j]);
- q(n,m-1,i);
- }
- if(n>m){
- //如果n大於m,使用m作爲分解,則要首先輸出m+的形式
- printf("%d+",m);
- //記錄下作爲樹幹節點m的值並使i自增
- set[i++]=m;
- //遞歸輸出m+以後的分解
- q(n-m,m,i);
- //遞歸完畢後需要將數組記錄後退一個,回到上一個節點,如上圖示過程2
- i--;
- //執行另一個分支,在下一次遞歸之前輸出記錄的數據,如上圖示過程3
- for(int j=0; j<i; j++)
- printf("%d+",set[j]);
- //遞歸輸出另一分支情況
- q(n,m-1,i);
- }
- }
- void main(){
- int n;
- while(scanf("%d",&n)!=EOF){
- if(n<=0){
- printf("Please input a positive interger.\n\n");
- continue;
- }
- k=n;