• 母函数&组合数学 HDU1028


    HDU1028,背包,树,母函数

    树:

    由n=8的解答树,可知该解答树有4个子树记作A[8][1]、A[8][2]、A[8][3]、A[8][4]

    A[8][1] = A[7][1] + A[7][2] + A[7][3] + 1.

    A[8][2] = A[6][2] + A[6][3] + 1

    A[8][3] = A[5][3] + 1

    A[8][4] = A[4][4] + 1

    记A[8][0]为n=8的总结点,则A[8][0] = A[8][1] + A[8][2] + A[8][3] + A[8][4] + 1

    由此规律,代码如下:

    #include<iostream>
    using namespace std;
    int main()
    {
    int A[125][125]={0};
    int n;
    //打表
    for(int k = 1;k <= 120;k++)  //都有等号
    {
    A[k][0] = 1;
    for(int i = 1; i <= k/2; i++){
    for(int j = i; j <= (k-i)/2; j++)  
    {
    A[k][i] += A[k-i][j];
    }
    A[k][i] += 1;//加自身的一种情况
    A[k][0] += A[k][i];//和为k的所有情况
    }
    }
    while(~scanf("%d",&n))
    {
    printf("%d ",A[n][0]);
    }
    return 0;
    }

    母函数:

     1 #include <iostream>
     2 using namespace std;
     3 // Author: Tanky Woo
     4 // www.wutianqi.com
     5 const int _max = 10001; 
     6 // c1是保存各项质量砝码可以组合的数目
     7 // c2是中间量,保存没一次的情况
     8 int c1[_max], c2[_max];   
     9 int main()
    10 {    //int n,i,j,k;
    11     int nNum;   // 
    12     int i, j, k;
    13  
    14     while(cin >> nNum)
    15     {
    16         for(i=0; i<=nNum; ++i)   // ---- ①
    17         {
    18             c1[i] = 1;
    19             c2[i] = 0;
    20         }
    21         for(i=2; i<=nNum; ++i)   // ----- ②
    22         {
    23  
    24             for(j=0; j<=nNum; ++j)   // ----- ③
    25                 for(k=0; k+j<=nNum; k+=i)  // ---- ④
    26                 {
    27                     c2[j+k] += c1[j];
    28                 }
    29             for(j=0; j<=nNum; ++j)     // ---- ⑤
    30             {
    31                 c1[j] = c2[j];
    32                 c2[j] = 0;
    33             }
    34         }
    35         cout << c1[nNum] << endl;
    36     }
    37     return 0;
    38 }

    ①  、首先对c1初始化,由第一个表达式(1+x+x^2+..x^n)初始化,把质量从0到n的所有砝码都初始化为1.

    ②  、 i从2到n遍历,这里i就是指第i个表达式,上面给出的第二种母函数关系式里,每一个括号括起来的就是一个表达式。

    ③、j 从0到n遍历,这里j就是(前面i個表达式累乘的表达式)里第j个变量,(这里感谢一下seagg朋友给我指出的错误,大家可以看下留言处的讨论)。如(1+x)(1+x^2)(1+x^3),j先指示的是1和x的系数,i=2执行完之后变为
    (1+x+x^2+x^3)(1+x^3),这时候j应该指示的是合并后的第一个括号的四个变量的系数。

    ④ 、 k表示的是第j个指数,所以k每次增i(因为第i个表达式的增量是i)。

    ⑤  、把c2的值赋给c1,而把c2初始化为0,因为c2每次是从一个表达式中开始的。

     

    母函数与组合数:

    首先回忆一下组合数:从n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m(m≤n)个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。

    母函数:形如:G(x) = a1*x + a2*x^2 + a3*x^3……的函数称为x的母函数。这种函数可以由几个表达式相乘实现。

      例如:掷两个骰子,求掷出n点有几种可能?(2<=n<=12)

      可以看成分布策略,并把每个骰子抽象化为x,每个骰子的点数用幂指数表示:

      (x^1 + x^2 + x^3 + x^4 + x^5 + x^6) * (x^1 + x^2 + x^3 + x^4 + x^5 + x^6)

      = ( x^2 + 2x^3 + 3x^4 + 4x^5 + 5x^6……x^12)

      结果中,每一项前面的系数就是所得点数的组合数。

    开始看了一个大牛的博客:http://www.wutianqi.com/?p=596   没看懂,换了一个,以下内容转自: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。

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

    通用模板

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

     1 //a为计算结果,b为中间结果。
     2  
     3 int a[MAX],b[MAX];
     4  
     5 //初始化a
     6  
     7 memset(a,0,sizeof(a));
     8  
     9 a[0]=1;
    10  
    11 for (int i=1;i<=17;i++)//循环每个因子,一个括号为一个因子
    12  
    13 {
    14  
    15 memset(b,0,sizeof(b));
    16  
    17 for (int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++)//循环每个因子的每一项
    18  
    19 for (int k=0;k+j*v[i]<=P;k++)//循环a的每个项
    20  
    21 b[k+j*v[i]]+=a[k];//把结果加到对应位
    22  
    23 memcpy(a,b,sizeof(b));//b赋值给a
    24  
    25 }

    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
    }
    

      

    例题

    下面看几个例题。

    一、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

  • 相关阅读:
    Mac php使用gd库出错 Call to undefined function imagettftext()
    centos 使用 locate
    Mac HomeBrew 安装 mysql
    zsh 命令提示符 PROMPT
    新的开始
    Java 面试题分析
    Java NIO Show All Files
    正确使用 Volatile 变量
    面试题整理 2017
    有10阶梯, 每次走1,2 or 3 阶,有多少种方式???
  • 原文地址:https://www.cnblogs.com/lyqf/p/9263485.html
Copyright © 2020-2023  润新知