[HDU1085][HDU1028][HDU2013] 組合數學入門(母函數、遞推)

先來說一說母函數,今天是第一次學。杭電關於母函數的PPT感覺不錯,挺適合入門看看的。
什麼是母函數?對於序列a0,a1,a2,…構造一函數:G(x)=a0+a1*x+a2*x^2+...G(x)就是序列a0,a1...的母函數。如若已知序列a0,a1,a2,…則對應的母函數G(x)便可根據定義給出。 反之,如若已經求得序列的母函數G(x),則該序列也隨之確定。 序列a0,a1,a2,…可記爲{an} 
如何用母函數來解決諸如整數拆分、郵票組合、砝碼稱重一類的問題?這一類問題大致是需要找到整數拆分的方案數,郵票可以組合出多少面額,某一種重量可以由多少種砝碼組合來完成稱重……當然利用類似動態規劃的思想往往也可以解決這類問題,下面討論怎麼使用母函數來完成。

下面這個例子來自HDU集訓的PPT


“例1:若有1克、2克、3克、4克的砝碼各一 枚,能稱出哪幾種重量?各有幾種可能方案? 
如何解決這個問題呢?考慮構造母函數。如果用x的指數表示稱出的重量,則:
  1個1克的砝碼可以用函數1+x表示,1個2克的砝碼可以用函數1+x2表示,
  1個3克的砝碼可以用函數1+x3表示,1個4克的砝碼可以用函數1+x4表示,
幾種砝碼的組合可以稱重的情況,可以用以上幾個函數的乘積表示:
(1+x)(1+x^2)(1+x^3)(1+x^4)
=(1+x+x^2+x^3)(1+x3+x^4+x^7)
=1+x+x^2+2x^3+2x^4+2x^5+2x^6+2x^7+x^8+x^9+x^10  
從上面的函數知道:可稱出從1克到10克,係數便是方案數。
理解了這個例子之後整數拆分、郵票組合、砝碼稱重一類的問題就都一併解決了。
下面來是我對這種母函數構造方式的理解。
對於上面的例1,如果取消砝碼個數的限制,則母函數變爲G(x)=(1+x+x^2+,,,,,)(1+x^2+x^4+...)(1+x^3+x^6+...)(1+x^4+x^8+..)
若要稱量重量M,那麼這個質量M就對應母函數展開後的x^M項。而x^M項的指數M按照數學上的展開來理解是什麼來的呢?
假設M=14,我們用1個1g砝碼,1個2g砝碼,1個3g砝碼和2個4g砝碼。則可以看做是從G(x)=(1+x+x^2+,,,,,)(1+x^2+x^4+...)(1+x^3+x^6+...)(1+x^4+x^8+..)的每一個括號(每個括號分別對應1g, 2g, 3g, 4g砝碼單獨可以組合出哪些質量)裏取出了x, x^2, x^3, x^8。然後意會一下爲什麼x^M項對應的係數就是稱量質量M的方案數了。如果理解的話這一類問題就都可以做了。
實現起來,展開G(x)函數的時候就是按照平時自己手工怎麼展開來做的。


兩個母函數練手題,性質都是一樣的

HDU1085(母函數) Holding Bin-Laden Captive!

http://acm.hdu.edu.cn/showproblem.php?pid=1085

硬幣面額1,2,5且有數量限制num1,num2,num3,問最小不能組合的數量是多少。

G(x)=(1+x+...+x^num1)(1+x^2+...+x^2num2)(1+x^5+,,,+x^5num3),展開,係數不爲0的數都是可以由硬幣組合出來的。

#include <cstdio>
#include <cstring>
using namespace std;

const int maxexp=1*1000+2*1000+5*1000+10;

int main()
{
    int n1, n2, n3;
    bool f;
    int c1[maxexp+1], c2[maxexp+1], c3[maxexp+1]; 
    
    while (scanf("%d%d%d", &n1, &n2, &n3)==3 && n1+n2+n3>0)
    {
        memset(c1, 0, sizeof(c1));
        memset(c2, 0, sizeof(c2));
        memset(c3, 0, sizeof(c3));
        for (int i=0; i<=n1; i++)               //1+x+x^2+...+x^n1
            c1[i]=1;
        for (int j=0; j<=n1; j++)
            for (int k=0; k<=2*n2; k+=2)        //1+x^2+...+x^(2*n2)
                    c2[j+k]+=c1[j];
        for (int j=0; j<=n1+2*n2; j++)
            for (int k=0; k<=5*n3; k+=5)        //1+x^2+...+x^(5*n2)
                    c3[j+k]+=c2[j];
        f=false;
        for (int j=0; j<=n1+2*n2+5*n3; j++)
            if (c3[j]==0)
            {
                printf("%d\n", j);
                f=true;
                break;
            }
        if (!f) printf("%d\n", n1+2*n2+5*n3+1);
    }
    return 0;
}

HDU1028(母函數) Ignatius and the Princess III

http://acm.hdu.edu.cn/showproblem.php?pid=1028

整數拆分。由於拆分結果只考慮有幾個1幾個2幾個3...不考慮順序什麼的,那麼問題就和之前的砝碼郵票什麼的一樣了。

G(x)=(1+x+...)(1+x^2+...)(1+x^3....)...(1+x^n)

n最大120,可以預處理一下保存結果,也可以一邊輸入一邊重新算,反正n很小……

#include <cstdio>
#include <cstring>
using namespace std;

const int maxexp=120;

int main()
{
    int n, now, pre;
    int f[2][maxexp+1];                         //"滾筒"使用 
    
    while (scanf("%d", &n)==1)
    {
        memset(f, 0, sizeof(f));
        for (int i=0; i<=n; i++)                //1+x+x^2+...+x^n
            f[1%2][i]=1;
        for (int i=2; i<=n; i++)
        {
            now=i%2; pre=(i-1)%2;
            for (int j=0; j<=n; j++)
                f[now][j]=0;
            for (int j=0; j<=n; j++)            //枚舉之前結果
                for (int k=0; k+j<=n; k+=i)     //枚舉當前多項式 1+x^i+x^2i+... 
                {
                    f[now][j+k]+=f[pre][j];             //中間結果保存在c2 
                }
        }
        printf("%d\n", f[n%2][n]);
    }
    return 0;
}



HDU2013

赤裸裸的遞推,毫無技術性而言

f(n)=(f(n-1)+1)*2, 從最後一天逆推。

#include <cstdio>
#include <cstdlib>

int main()
{
    int f[32], n;
    f[1]=1;
    for (int i=2; i<30; i++)
        f[i]=(f[i-1]+1)*2;
    while (scanf("%d", &n)==1)
        printf("%d\n", f[n]);
    return 0;
}


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