整數劃分問題

2021-07-25 19:46:53 字數 3830 閱讀 2033

題意:給你乙個整數n,求n的劃分種類總數

這是一道很經典的整數劃分問題。

所謂整數劃分,是指把乙個正整數n寫成如下形式:

n=m1+m2+...+mi; (其中mi為正整數,並且1 <= mi <= n),則為n的乙個劃分。

如果中的最大值不超過m,即max(m1,m2,...,mi)<=m,則稱它屬於n的乙個m劃分。

思路

一、遞迴法

我們將最大加數不大於m的劃分個數記作q(n,m).

可以建立q(n,m)的如下遞迴關係:

(1)q(n,1)=1,n>=1

當最大加數不大於1時,任何正整數n只有一種劃分形式,即1+1+1+……+1

(2)q(n,m)=q(n,n),m>=n

最大加數實際上不能大於n,因此q(1,m)=1

(3)q(n,n)=1+q(n,n-1)

正整數n的劃分有最大加數=n的劃分和最大加數<=n-1的劃分組成

(4)q(n,m)=q(n,m-1)+q(n-m,m), n>m>1

正整數n的最大加數不大於m的劃分由最大加數=m的劃分和劃分和最大加數<=m-1的劃分組成

(a). 劃分中包含 m 的情況,即 }, 其中  的和為 n - m,可能再次出現 m,因此是(n - m)的 m 劃分,因此這種劃分個數為 q(n-m, m);

(b). 劃分中不包含 m 的情況,則劃分中所有值都比 m 小,即 n 的 ( m - 1 ) 劃分,個數為 q(n, m - 1);

#include int n,ans; 

int fix(int pn,int pm);

int main()

return 0;

} int fix(int pn,int pm)

for(i=2; i<=n; i++)

} } int main()

{ memset(dp,0,sizeof(dp));

while(~scanf("%d",&n))

{ sovle();

cout<

三、母函式   (參考了

下面我們從另乙個角度即「母函式」的角度來考慮這個問題。

所謂母函式,即為關於x的乙個多項式g(x):

有 g(x)= a0 + a1*x + a2*x^2 + a3*x^3 + ...

則我們稱g(x)為序列(a0,a1,a2,...)的母函式。關於母函式的思路我們不做更多分析。

我們從整數劃分考慮,假設n的某個劃分中,1的出現個數記為a1,2的個數記為a2,..., i的個數記為ai,

顯然: ak<=n/k; (0<= k <=n)

因此n的劃分數f(n,n),也就是從1到n這n個數字中抽取這樣的組合,每個數字理論上可以無限重複出現,即個數隨意,使他們的總和為n。顯然,數字i可以有如下可能,出現0次(即不出現),1次,2次,..., k次,等等。把數字i用(x^i)表示,出現k次的數字i用 x^(i*k)表示, 不出現用1表示。例如數字2用x^2表示,2個2用x^4表示,3個2用x^6表示,k個2用x^2k表示。

則對於從1到n的所有可能組合結果我們可以表示為:

g(x) = (1+x+x^2+x^3+...+x^n) (1+x^2+x^4+...) (1+x^3+x^6+...) ... (1+x^n)

= g(x,1) g(x,2) g(x,3) ... g(x, n)

= a0 + a1* x + a2* x^2 + ... + an* x^n + ... ;  (展開式)

上面的表示式中,每乙個括號內的多項式代表了數字i的參與到劃分中的所有可能情況。因此該多項式展開後,由於x^a * x^b=x^(a+b),因此 x^i 就代表了i的劃分,展開後(x^i)項的係數也就是i的所有劃分的個數,即f(n,n)=an (上式中g(x,i)表示數字i的所有可能出現情況)。

由此我們找到了關於整數劃分的母函式g(x);剩下的問題是,我們需要求出g(x)的展開後的所有係數。

為此我們首先要做多項式乘法,對於我們來說並不困難。我們把乙個關於x的一元多項式用乙個整數陣列a表示,a[i]代表x^i的係數,即:

g(x) = a[0] + a[1]x + a[2]x^2 + ... + a[n]x^n;

則關於多項式乘法的**如下,其中陣列a和陣列b表示兩個要相乘的多項式,結果儲存到陣列c:

#define n 130

unsigned long a[n];/*多項式a的係數陣列*/

unsigned long b[n];/*多項式b的係數陣列*/

unsigned long c[n];/*儲存多項式a*b的結果*/

/*兩個多項式進行乘法,係數分別在a和b中,結果儲存到c ,項最大次數到n */

/*注意這裡我們只需要計算到前n項就夠了。*/

void poly()

{ int i,j;

memset(c,0,sizeof(c));

for(i=0; i

下面我們求出g(x)的展開結果,g(x)是n個多項式連乘的結果:

/*計算出前n項係數!即g(x,1) g(x,2)... g(x,n)的展開結果*/

void init()

{ int i,k;

memset(a,0,sizeof(a));

memset(c,0,sizeof(c));

for(i=0;i

通過以上的**,我們就計算出了g(x)的展開後的結果,儲存到陣列c中。此時有:f(n,n)=c[n];剩下的工作只是把相應的陣列元素輸出即可。

問題到了這裡已經解決完畢。但我們發現,針對該問題,g(x,k)是乙個比較特殊的多項式,特點是只有k的整數倍的索引位置有項,而其他位置都為0,具有項「稀疏」的特點,並且項次分布均勻(次數跨度為k)。這樣我們就可以考慮在計算多項式乘法時,可以減少一些迴圈。因此可以對poly函式做這樣的乙個改進,即把k作為引數傳遞給poly:

/*兩個多項式進行乘法,係數分別在a和b中,結果儲存到c ,項最大次數到n */

/*改進後,多項式a乘以乙個有特殊規律的多項式b,即b中只含有x^(k*i)項,i=0,1,2,*/

/*如果b沒有規律,只需要把k設為1,即與原來函式等效*/

void poly2(int k) /*引數k的含義:表示b中只有b[k*i]不為0!*/

{ int i,j;

memset(c,0,sizeof(c));

for(i=0; i

整體**如下:

#include #include using namespace std;

#define n 130

typedef long long ll;

ll a[n],b[n],c[n];

int n;

void poly(int m);

void init();

int main()

{ init();

while(cin>>n)

{cout<

母函式這個方法我是想不到的,請原諒我才疏學淺,不過學會如何獲取知識也是種能力,所以大家碰到不會的也不要氣餒,看看前人是否已經給你做好充足的準備!

整數劃分問題

整數劃分問題是乙個經典問題,幾乎在講演算法設計的書中都會講,下面把主要的思想給總結下。所謂整數劃分,就是將乙個正整數n劃分為一系列的正整數之和,如將n可以劃分為 1 我們該如何找出所有的劃分呢?我們可以先來看看整數劃分的規律 譬如正整數 6 劃分情況如下 6 5 14 2 4 1 1 3 3 3 2...

整數劃分問題

給定乙個自然數,分成k部分,a1,a2.的數的和,要求a1 a2.求有多少種?原理 整數n拆分成最多不超過m個數的和的拆分數,和n 拆分成最大不超過m的拆分數相等。根據這個原理,原問題就轉化成了求最大拆分為k的拆分個數與最大拆分為k 1的拆分個數的差 f n,k f n,k 1 f n k,k 如下...

整數劃分問題

首先是遞迴解法 整數劃分問題是將乙個正整數n拆成一組數連加並等於n的形式,且這組數中的最大加數不大於n。如6的整數劃分為 65 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種。下面介紹一種通過遞迴方法得到乙...