母函數詳解和史上最通用最高效的母函數模板

母函數,又稱生成函數,是ACM競賽中經常使用的一種解題算法,常用來解決組合方面的題目。

本文講解母函數,但不講解該算法的基礎理論。讀者隨便找一本組合數學教材便可找到相應的內容,或者直接在網上搜索一下。

母函數通常解決類似如下的問題:

給5張1元,4張2元,3張5元,要得到15元,有多少種組合?

某些時候會規定至少使用3張1元、1張2元、0張5元。

某些時候會規定有無數張1元、2元、5元。

……


解題過程

解題時,首先要寫出表達式,通常是多項的乘積,每項由多個x^y組成。如(1+x+x^2)(1+x^4+x^8)(x^5+x^10+x^15)。

通用表達式爲

(x^(v[0]*n1[0])+x^(v[0]*(n1[0]+1))+x^(v[0]*(n1[0]+2))+...+x^(v[0]*n2[0]))
(x^(v[1]*n1[1])+x^(v[1]*(n1[1]+1))+x^(v[1]*(n1[1]+2))+...+x^(v[1]*n2[1]))
...
(x^(v[K]*n1[K])+x^(v[K]*(n1[K]+1))+x^(v[K]*(n1[K]+2))+...+x^(v[K]*n2[K]))

K對應具體問題中物品的種類數。

v[i]表示該乘積表達式第i個因子的權重,對應於具體問題的每個物品的價值或者權重。

n1[i]表示該乘積表達式第i個因子的起始係數,對應於具體問題中的每個物品的最少個數,即最少要取多少個。

n2[i]表示該乘積表達式第i個因子的終止係數,對應於具體問題中的每個物品的最多個數,即最多要取多少個。

對於表達式(1+x+x^2)(x^8+x^10)(x^5+x^10+x^15+x^20),v[3]={1,2,5},n1[3]={0,4,1},n2[3]={2,5,4}。

解題的關鍵是要確定v、n1、n2數組的值。

通常n1都爲0,但有時候不是這樣。

n2有時候是無限大。

之後就實現表達式相乘,從第一個因子開始乘,直到最後一個爲止。此處通常使用一個循環,循環變量爲i。每次迭代的計算結果放在數組a中。計算結束後,a[i]表示權重i的組合數,對應具體問題的組合數。

循環內部是把每個因子的每個項和a中的每個項相乘,加到一個臨時的數組b的對應位(這裏有兩層循環,加上最外層循環,總共有三層循環),之後就把b賦給a。

這些過程通常直接套用模板即可。

通用模板

下面我直接給出通用模板:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. //爲計算結果,b爲中間結果。  
  2. int a[MAX],b[MAX];  
  3. //初始化a  
  4. memset(a,0,sizeof(a));  
  5. a[0]=1;  
  6. for (int i=1;i<=17;i++)//循環每個因子  
  7. {  
  8.     memset(b,0,sizeof(b));  
  9.     for (int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++)//循環每個因子的每一項  
  10.         for (int k=0;k+j*v[i]<=P;k++)//循環a的每個項  
  11.             b[k+j*v[i]]+=a[k];//把結果加到對應位  
  12.     memcpy(a,b,sizeof(b));//b賦值給a  
  13. }  

P是可能的最大指數。拿鈔票組合這題來說,如果要求15元有多少組合,那麼P就是15;如果問最小的不能拼出的數值,那麼P就是所有錢加起來的和。P有時可以直接省略。具體請看本文後面給出的例題。

如果n2是無窮,那麼第二層循環條件j<=n2[i]可以去掉。


如何提高效率?

用一個last變量記錄目前最大的指數,這樣只需要在0..last上進行計算。

這裏給出第二個模板:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. //初始化a,因爲有last,所以這裏無需初始化其他位  
  2. a[0]=1;  
  3. int last=0;  
  4. for (int i=0;i<K;i++)  
  5. {  
  6.     int last2=min(last+n[i]*v[i],P);//計算下一次的last  
  7.     memset(b,0,sizeof(int)*(last2+1));//只清空b[0..last2]  
  8.     for (int j=n1[i];j<=n2[i]&&j*v[i]<=last2;j++)//這裏是last2  
  9.         for (int k=0;k<=last&&k+j*v[i]<=last2;k++)//這裏一個是last,一個是last2  
  10.             b[k+j*v[i]]+=a[k];  
  11.     memcpy(a,b,sizeof(int)*(last2+1));//b賦值給a,只賦值0..last2  
  12.     last=last2;//更新last  
  13. }  

例題

下面看幾個例題。

一、hdu 1085hdu 1171兩題套用了第二個模板,省略了代碼中二三層循環裏關於last2的條件(其實也可以加上)。

詳見:

hdu 1085:http://blog.csdn.net/xiaofei_it/article/details/17041467

hdu 1171:http://blog.csdn.net/xiaofei_it/article/details/17041709

二、hdu 1398套用了第一個模板,因爲n2中每一項爲無窮大,所以n2數組就省略了。

詳見:

hdu 1398:http://blog.csdn.net/xiaofei_it/article/details/17041815

三、hdu 2079hdu 2082hdu 2110三題直接套用了第二個模板。

詳見:

hdu 2079:http://blog.csdn.net/xiaofei_it/article/details/17042045

hdu 2082:http://blog.csdn.net/xiaofei_it/article/details/17042253

hdu 2110:http://blog.csdn.net/xiaofei_it/article/details/17042421

另外,至於什麼時候用第一個模板,什麼時候用第二個模板,就看題目規模。

通常情況下,第一個模板就夠用了,上面的那些用第二個模板的題目用第一個模板同樣能AC。

但如果數據規模比較大(通常不會有這種情況),就要使用第二個模板了。

以上題目n1均爲0。

四、hdu 2152是一道n1不爲0的題目,我在這裏分別套用第一個和第二個模板解題。

詳見:

hdu 2152:http://blog.csdn.net/xiaofei_it/article/details/17042497


希望大家讀了我的這篇文章後能有收穫。


轉自 : http://blog.csdn.net/xiaofei_it/article/details/17042651

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章