• 母函数模板核心


    转载至

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

     先理解一个简单的

    母函数,又称生成函数,是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。

    这些过程通常直接套用模板即可。

    通用模板

    下面我直接给出通用模板一:

    //为计算结果,b为中间结果。
    int a[MAX],b[MAX];
    //初始化a
    memset(a,0,sizeof(a));
    a[0]=1;
    for (int i=1;i<=17;i++)//循环每个因子
    {
        memset(b,0,sizeof(b));
        for (int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++)//循环每个因子的每一项
            for (int k=0;k+j*v[i]<=P;k++)//循环a的每个项
                b[k+j*v[i]]+=a[k];//把结果加到对应位
        memcpy(a,b,sizeof(b));//b赋值给a
    }

    P是可能的最大指数。拿钞票组合这题来说,如果要求15元有多少组合,那么P就是15;如果问最小的不能拼出的数值,那么P就是所有钱加起来的和。P有时可以直接省略。具体请看本文后面给出的例题。
    如果n2是无穷,那么第二层循环条件j<=n2[i]可以去掉。

    如何提高效率?

    用一个last变量记录目前最大的指数,这样只需要在0..last上进行计算。

    这里给出第二个模板:

    //初始化a,因为有last,所以这里无需初始化其他位
    a[0]=1;
    int last=0;
    for (int i=0;i<K;i++)
    {
        int last2=min(last+n[i]*v[i],P);//计算下一次的last
        memset(b,0,sizeof(int)*(last2+1));//只清空b[0..last2]
        for (int j=n1[i];j<=n2[i]&&j*v[i]<=last2;j++)//这里是last2
            for (int k=0;k<=last&&k+j*v[i]<=last2;k++)//这里一个是last,一个是last2
                b[k+j*v[i]]+=a[k];
        memcpy(a,b,sizeof(int)*(last2+1));//b赋值给a,只赋值0..last2
        last=last2;//更新last
    }
  • 相关阅读:
    [六、常用控件]1UIButton控件的使用
    [五、视图控制器]18在故事板中设置初始视图控制器
    [五、视图控制器]13视图控制器和滚动视图
    [五、视图控制器]16导航视图控制器的入栈和出栈
    [五、视图控制器]17自定义导航控制器的导航按钮
    [五、视图控制器]12UITapGestureRecognizer手势之双击
    [五、视图控制器]15导航视图控制器的使用
    [五、视图控制器]19在故事板中使用标签和按钮控件
    [五、视图控制器]11UITapGestureRecognizer手势之长按
    [六、常用控件]4UILabel自定义文字样式
  • 原文地址:https://www.cnblogs.com/-citywall123/p/9915404.html
Copyright © 2020-2023  润新知