題意:給你乙個整數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種。下面介紹一種通過遞迴方法得到乙...