母函數(對於初學者的最容易理解的)

普通母函數:

在用到母函數之前啊,恐怕很少有人聽過母函數,我也一樣,我當時是做杭電acm的2082題要用母函數做纔去瞭解母函數的。當然母函數分爲很多種,包括普通母函數、指數母函數、L級數、貝爾級數和狄利克雷級數,我這裏說的都是普通母函數。

什麼是普通母函數呢,——把組合問題的加法法則和冪級數的的乘冪的相加對應起來,這句話可能一開始難以理解,不過其實學完了之後很容易理解,母函數的思想很簡單—就是把離散數列和冪級數一一對應起來,把離散數列間的相互結合關係對應成爲冪級數間的運算關係,最後由冪級數形式來確定離散數列的構造。

我就從那個經典的砝碼的例子講起。

題目:有1克、2克、3克、4克的砝碼各一 枚,能稱出哪幾種重量?每種重量各有幾種可能方案? 

窮舉的話很容易就可以得出結果,但是很顯然這種類型的窮舉需要花費的時間是n4方級別,計算機還可以在比較短的而時間內算出來,但是如果給出m種砝碼,那麼就是nm次方級別,那麼如果砝碼種類一多,計算機恐怕就無法在短時間內給出答案。而通過用母函數可以把這類題的時間規模壓縮到n3級別,使得計算機能很快給出解答。

我們先來看看窮舉的過程,先假設使用一個1g的砝碼,然後再假設在此情況下,使用2g的砝碼,在此基礎上,再假設使用一個3g的,再假設使用一個4g的,這就是1中情況,顯然,這種方案稱出的重量爲10g,然後改變上面的一個假設條件,比如假設沒有使用4g的,只是用了1g、2g、3g,顯然這種方案稱出的是6g,以此類推,將所有的可能列舉出來後即可得到全部的方案,1g的砝碼有兩種情況,使用還是不使用,其他的砝碼也一樣,每一種1g砝碼的狀態都可以有兩種砝碼狀態,以此類推總共就有2*2*2*2=16中方案,當然其中包含一種一個砝碼也沒有的0g方案。

我們最後可以窮舉出結果:1g、2g、8g、9g、10g各有一種方案,3g、4g、5g、6g、7g各有兩種,總共十五種,這是在不算一種0g的方案的時候,算上就是16種,和上面的過程相符。

我們可以用一個類似離散數學的邏輯式表示前兩種砝碼組合出來的情況

我在這裏使用||表示析取,也就是編程時的“邏輯或”;用&&表示合取,也就是編程時的“邏輯與”,析取與合取都是離散數學中的概念,其實就等於編程語言中的邏輯與和邏輯或,析取與合取的符號ˆ和ˇ很多人雖學過並不熟悉,我就用編程語言中的符號代替了,熟悉離散數學的人可以轉換成析取與合取的符號。

(使用1g || 不使用1g)&&(使用2g || 不使用2g)

=使用1g&&使用2g || 不使用1g&&使用2g || 使用1g&&不使用2g || 不使用1g&&不使用2g

每個用 “||”分開的一項都是一種方案。“&&”表示選擇第一項的情況下有選擇了第二項。

大家可以發現這個表達式和一種表達式很像,沒錯,如果把“||”看成加法,“&&”看成乘法,和多項式的乘法一模一樣。那麼我們直覺的想到,有沒有可能用多項式乘法來表示組合的情況呢?我們再來看題目,題目需要的是幾種砝碼組合後的重量,是一個加法關係,但是在上式中“&&”是一種類似於乘法的運算關係,這怎麼辦呢?有沒有什麼這樣一種運算關係,以乘法的形式運算,但是結果表現出類似於加法的關係呢?正好有一個,那就是冪運算。Xm 乘上Xn結果是Xm+n,他完美的符合了我們的要求。那麼以次數表示砝碼的質量, 就可以以多項式的形式表示砝碼組合的所有方案。

還是以前兩個砝碼爲例說明,表示1g砝碼的兩種狀態的多項式就是(x0+x1),表示2g的就是(x0+x2),x0表示沒有使用砝碼,所以重量爲0,當然了因爲x0 =1,所以也可以寫成1,後面爲了方便我會這麼寫。注意,砝碼的質量是以次數表示的,而不是直覺上的用下標表示爲x1,x2。這點很重要,不然的話就無法表現出冪運算的關係了。

(x0+x1)*(x0+x2)

=x0*x0 + x1*x0 + x0*x2 + x1*x2

=x0 + x1 + x2 + x3

很顯然,有四種方案,0g、1g、2g、3g,結果與我們窮舉的結果相同,而如果結果中有相同的項,那麼合併同類項後每一項的係數就是這種重量有幾種實現方案,這會在我們用此方法解決4個砝碼問題的時候得到證明。

那麼接下來試試用這種表達式表示4個砝碼的組合情況

(x0+x1)* (x0+x2) * (x0+x3)* (x0+x4)

=x0 + x1 + x2 + 2x3 + 2x4 + 2x5 + 2x6 + 2x7 + x8+ x9 + x10 

具體的計算過程我就不細寫了,多項式乘法大家都應該會。

結果與上面窮舉的結果是相同的,次數代表組合後可稱出的質量,係數代表組合出這種質量的方案的數量。至此也就得出了答案。這就是普通母函數,現在你可以回頭去看看我前面說的那兩句話——把組合問題的加法法則和冪級數的的乘冪的相加對應起來,把離散數列間的相互結合關係對應成爲冪級數間的運算關係,最後由冪級數形式來確定離散數列的構造。是不是明白了母函數的本質了呢?是不是恍然大悟了呢?

接下來我們把開始的題目稍稍變化一下:

求用1g、2g、3g的砝碼稱出不同重量的方案數 。

大家把這種情況和第一種比較有何區別?第一種每種是一個,而這裏每種是無限的。

怎樣在母函數裏表現出無限這種性質呢?很簡單,我們以2g砝碼爲例,因爲我們有無限個2g砝碼,所以我們可以把2個2g砝碼看成是4g砝碼,3個2g砝碼看成是6g砝碼,依次類推,把m個n g砝碼看成是一個n*m g砝碼,還是先以前兩個砝碼爲例,那麼多項式相應的就變成

(x0 +x1 + x2 + x3 + x4 + x5 + …… )*(x0 + x2 + x4 + x6 + x8+ x10 + ……)

結果自然也是無限的,但是這種問題在實際問題中,一定會給出一個確定的值,比如說求組成10g的方案有幾種。那麼我們就只要在合併後的結果求到最高次的項是x10即可,後面的項可以忽略不計。那麼要結果中最高爲10次方,開始每一種砝碼的無限項的表達式該寫到幾次呢?也是10次,因爲表達式中最低的項有x0 ,所以想在結果中不漏掉出現x10 的項,必須乘之前的項最高的項不能小於x10,而表達式中不可能出現x-1,,所以x11 和任何一項相乘都會大於x10,所以x11 是不需要的,但寫上也無妨,不影響結果,但是如果可以有x10(比如3g的砝碼組合不出10g的,但是2g的就可以),那就必須寫,不然就會漏掉一些方案。

那麼如果題目是求用1g、2g、3g的砝碼稱出10g的方案數 。

表達式就是:

(x0 +x1 + x2 + x3 + x4 + x5 + ……x10 )*(x0 + x2 + x4 + x6 + x8+ x10 )

結果就是合併同類項後x10的係數。

多項式乘法顯然是一種運算時間規模在n2級別的運算,但是如果循環生成無限的砝碼,也就是上面用小砝碼組合出每一種可能的大砝碼,那麼生成多項式就又需要n的規模,在此基礎上進行多項式乘法,最後無限砝碼的問題需要的運算時間規模就是n3級別。

這樣母函數就把這類組合問題從nn級別轉化成了n3級別。這便是母函數的奇妙與威力。

知道了母函數的原理,用程序來實現也就不是什麼困難的事了,其實就是做多項式乘法的程序

用c++實現算出無限砝碼情況下的某種重量的方案數。
 

#include <iostream>    
using namespace std;    
// Author: bjr    
//     
const int max = 1000;     
// sup是保存多項式的數組,sup[n]中的值代表xn的係數  
// temp是臨時多項式,保存相乘的臨時中間情況    
int sup[max], temp[max];     
/* 
程序始終只計算兩個多項式之間的乘積,多個多項式的情況 
先計算前兩個的乘積,將結果作爲第一個多項式,再與第三個相乘 
依次類推,sup始終存放當前運算後的結果然後作爲被乘多項式, 
*/    
int main()    
{     
    int target;   //  目標重量, 比如上面的例子裏就是10,要<max的值  
    int i, j, k;    
     
    while(cin >> target)    
    {    
        for(i=0; i<=target; ++i)       
        {    
            sup[i] = 1;     
//初始化第一個多項式,也就是用1g砝碼的多項式,  
//注意如果題目沒給1g的砝碼那麼就不能++i,而要加上砝碼的質量  
            temp[i] = 0;    
//將臨時區清空,無論第一個多項式質量是幾都要全部置零  
        }    
        for(i=2; i<=target; ++i)     
// 生成後續的第i個多項式,此題中是2g,i從2開始。  
//如果砝碼的值不是規律增長,i可能需要取決於輸入  
        {    
     
            for(j=0; j<=target; ++j)     
// 遍歷當前結果多項式的每一項(當前結果的第j項)與第i個多項式相乘,  
                for(k=0; k+j<=target; k+=i)   
// 遍歷第i個多項式的每一項,此處構造用小砝碼組成大砝碼的多項式  
                {    
                    temp[j+k] += sup[j];    
//冪運算,注意理解  
                }    
            for(j=0; j<=target; ++j)      
// 將臨時的結果覆蓋當前結果,同時把臨時結果置零,爲下次做準備  
            {    
                sup[j] = temp[j];    
                temp[j] = 0;    
            }    
        }    
        cout << sup[target] << endl;  //輸出結果  
    }    
    return 0;    
}

指數函數:

指數型母函數:(用來求解多重集的排列問題)

    n個元素,其中a1,a2,····,an互不相同,進行全排列,可得n!個不同的排列。

 若其中某一元素ai重複了ni次,全排列出來必有重複元素,其中真正不同的排列數應爲 clip_image001,即其重複度爲ni!

  同理a1重複了n1次,a2重複了n2次,····,ak重複了nk次,n1+n2+····+nk=n。

    對於這樣的n個元素進行全排列,可得不同排列的個數實際上是 clip_image002

 若只對其中的r個元素進行排列呢,那就用到了指數型母函數。

    構造母函數G(x)=clip_image003+clip_image004則稱G(x)是數列a0,a1…an的指數型母函數。

 一般過程:

        1.建立模型:物品n種,每種數量分別爲k1,k2,..kn個,求從中選出m個物品的排列方法數。

     2.構造母函數:G(x)=(1+ clip_image005+clip_image006 …+clip_image007)(1+ clip_image005[1]+clip_image006[1]+…clip_image008)…(1+ clip_image005[2]+clip_image006[2]+…clip_image009)

                                      =a0+a1·x+ clip_image010 · clip_image011 +clip_image012 · clip_image013+…clip_image014 · clip_image015 (其中pp=k1+k2+k3…kn)

      G(x)含義:ai爲選出i個物品的排列方法數。

           若題中有限定條件,只要把第i項出現的列在第i項的式中,未出現的不用列入式中。

   如:物品i出現的次數爲非0偶數,則原式改爲…*(   clip_image002[4] + clip_image002[6] + clip_image002[8]    )*…

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
 
using namespace std;
 
typedef long long ll;
 
const int maxn = 1e5 + 10;
#define mem(a) memset(a, 0, sizeof a)
 
double a[maxn],b[maxn]; // 注意爲浮點型
 
int s1[maxn];
 
double f[11];
void init() {
    mem(a);
    mem(b);
    mem(s1);
    f[0] = 1;
    for (int i = 1; i <= 10; i++) {
        f[i] = f[i - 1] * i;
    }
}
 
int main() {
    int n,m;
    while (~scanf("%d%d", &n, &m)) {
       init();
       for (int i = 0; i < n; i++) {
            scanf("%d", &s1[i]);
       }
        for (int i = 0; i <= s1[0]; i++) a[i] = 1.0 / f[i];
        for (int i = 1; i < n; i++) {
            mem(b);
            for (int j = 0; j <= m; j++) {
                for (int k = 0; k <= s1[i] && k + j <= m; k++) {
                    b[j + k] += a[j] * 1.0 / f[k]; //注意這裏
                }
            }
            memcpy(a, b, sizeof b);
        }
       printf("%.0f\n", a[m] * f[m]);
    }
    return 0;
}

 

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