• 生成函数/母函数入门学习


     开始学生成函数了,开篇博客记录一下学习过程,现在还只是学了一些很浅显的内容,还会不断更新。

    首先推荐两篇写得很好的博客:https://blog.csdn.net/consciousman/article/details/77935700

    https://blog.csdn.net/wu_tongtong/article/details/78854572#comments

     

    生成函数或者叫母函数,是解决组合数学尤其是计数问题的一大利器。生成函数有许多种,最为常见也是应用最广泛的两种是普通母函数和指数型母函数。形式上说,普通型生成函数用于解决多重集的组合问题,而指数型母函数用于解决多重集的排列问题。母函数还可以解决递归数列的通项问题(例如使用母函数解决斐波那契数列的通项公式)。(来自百度百科)

    普通母函数是用来解决多重集的组合问题的。它的原理是:“母函数的思想很简单—就是把离散数列幂级数一一对应起来,把离散数列间的相互结合关系对应成为幂级数间的运算关系,最后由幂级数形式来确定离散数列的构造. “(来自百度百科)。这句话初听起来很蒙,但是一旦理解了母函数,就能明白这句话就是母函数的本质。

    这里给出比较通俗的解释(也许就有失本意了):假如有n个集合,这n个集合元素个数分别是k1,k2,k3...kn,一个集合的元素是相同的。那么要从这n个集合中的元素中取m个组成一个组合(不计顺序)。那么我们构造生成函数A1=(x^0+x^1+x^2+...x^k1) , A2=(x^0+x^1+...x^k2)... An=(x^0+x^1+...x^kn)  然后令 B=(A1)*(A2)*...*(An)。得到的B应该是这样子的,B=a0*x^0+a1*x^1+...an*x^n+...∞

    于是B的每一项都是一种组合计算的结果,每一项的幂代表需要组合的元素个数m(即从n个集合中取m个元素),每一项的系数就是选出m个元素的组合方案数。例如第B的第i项为 ai*x^i 代表从n个集合中的i组合数为ai。

    生成函数巧妙地运用了多项式乘法的性质,两项相乘:幂数相加,系数相乘。幂代表着选择个数,系数代表着方案数。于是所以n个多项式相乘后的结果就是最终的组合结果。

    母函数的时间限制主要是多项式乘法,可以暴力n^2一项一项乘,也可以用nlogn的FFT计算。

    暴力算的模板,以HDU1028的整数划分问题为例:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int N=125;
    int n,g[2][N];  //滚动数组保存前i个多项式相乘结果,g[i]代表:指数为i的项的系数为g[i] 
    
    int main()
    {
        while (scanf("%d",&n)==1) {
            for (int i=0;i<=n;i++) g[1][i]=1,g[0][i]=0;  //初始化 
            for (int i=2;i<=n;i++) {  //计算前i个砝码的结果 
                for (int j=0;j<=n;j+=i)  //j控制第i个砝码的多项式:(1+x^i+x^2i+...) 
                    for (int k=0;j+k<=n;k++)  //k控制前面(i-1)项的结果相乘 
                        g[i%2][j+k]+=g[(i%2)^1][k];    
                for (int j=0;j<=n;j++) g[(i%2)^1][j]=0;            
            }
            printf("%d
    ",g[n%2][n]);
        }
        return 0;
    }

    指数型母函数是用来解决多重集的排列问题的。只要理解了普通母函数就很容易理解指数型母函数,指数型母函数只是在初始式子上有些不同。

    我们知道多重集的排列公式为:n! / (n1! * n2! * n3! ... nk!) 。普通母函数已经算出了多重集的组合数,在此基础上乘上多重集的排列公式即可解决多重集的排列问题。

    那么指数型生成函数为:A1=(x^0/0!+x^1/1!+x^2/2!+...x^k1/k1!) A2=A1=(x^0/0!+x^1/1!+x^2/2!+...x^k2/k2!) .....可以发现指数型母函数只是在每一项除多了个 个数的阶乘。这样计算出来后把结果再乘上其总排列总个数的阶乘,这样便得到了上诉的多重集排列公式。

    由于存在出除法,暴力求指数型母函数需要用double类型来存储。

    暴力n^2计算指数型母函数,以HDU-1521为例:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=12;
    int n,m,a[N];
    double f[N],g[2][N];
    
    int main()
    {
        while (cin>>n>>m) {
            memset(g,0,sizeof(g));
            for (int i=1;i<=n;i++) scanf("%d",&a[i]);
            f[0]=1; for (int i=1;i<=m;i++) f[i]=f[i-1]*i;
            
            for (int j=0;j<=a[1];j++) g[1][j]=1.0/f[j];
            for (int i=2;i<=n;i++) {  //前i个集合 
                for (int j=0;j<=a[i];j++)  //j为第i个集合的幂 
                    for (int k=0;j+k<=m;k++)  //k为前i-1个集合的幂 
                        g[i%2][j+k]+=g[(i%2)^1][k]/f[j];
                for (int j=0;j<=m;j++) g[(i%2)^1][j]=0;        
            }
            printf("%.0lf
    ",f[m]*g[n%2][m]);
        }
        return 0;
    }

    母函数的化简:以上其实只是最最最基础的内容。学会了以上内容其实也不能做很多的题,因为常常会有的题目的N十分巨大,即使你看出了朴素的母函数,但是也不能在规定时间内求出来(即使用FFT),或者这个母函数根本就没办法算,这时候就需要化简母函数使他变得更简单。

    化简母函数需要积累大量的常见化简公式,这一块还是看上面两个大佬的博客吧,大佬写得很详细。

    题目练习

  • 相关阅读:
    Ext JS学习第三天 我们所熟悉的javascript(二)
    Ext JS学习第二天 我们所熟悉的javascript(一)
    Ext JS学习第十七天 事件机制event(二)
    Ext JS学习第十六天 事件机制event(一)
    Ext JS学习第十五天 Ext基础之 Ext.DomQuery
    Ext JS学习第十四天 Ext基础之 Ext.DomHelper
    Ext JS学习第十三天 Ext基础之 Ext.Element
    Ext JS学习第十天 Ext基础之 扩展原生的javascript对象(二)
    针对错误 “服务器提交了协议冲突. Section=ResponseHeader Detail=CR 后面必须是 LF” 的原因分析
    C# 使用HttpWebRequest通过PHP接口 上传文件
  • 原文地址:https://www.cnblogs.com/clno1/p/10813884.html
Copyright © 2020-2023  润新知