zoukankan      html  css  js  c++  java
  • 母函数&组合数学 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

  • 相关阅读:
    Oracle最大连续访问天数
    oracle中MINUS
    sql中含有中文,export oralce编码格式的环境变量
    alternate_file_dcol_rollback
    oracle查询分区表
    hive创建表sql
    使用ANSI改变终端输出样式
    Golang中的空字符,似花不是花
    程序员必看 Linux 常用命令(重要)
    MongoDB入门介绍与案例分析
  • 原文地址:https://www.cnblogs.com/lyqf/p/9263485.html
Copyright © 2011-2022 走看看