组合型母函数程序实现

       母函数或者生成函数是一个神奇的东西,对ACMer来说它省去了排列组合等等规律的考虑,我们只需会列出多项式,会计算多项式乘法,最后选择问题需要的项的系数进行输出即可。至少初级题目可以这样做。
    对于任意数列{ai,i=0,1,...,}将其写作
                                 G(x) = a0 + a1·x + a2 · x^2 + a3·x^3 + …
G(x)就叫做数列{ai}的母函数。x是什么呢?who cares, whatever. 你可以把x看做是占位符,x的某次方就是一个坑。你需要做的就是把对应的系数放到相应的坑里。因此有数学洁癖的人会强调说母函数不是函数,不是映射。
       母函数可以应用的关键在于,G(x)还可以写成是若干个较短的多项式相乘的形式,而这若干个短式子都是非常容易求出的。因此G(x)也可以求出,上式右边任何项的系数都可以求出。
       Note,以下说的是求组合的母函数,求排列的母函数有点不同。
      以hdu1028为例 http://acm.hdu.edu.cn/showproblem.php?pid=1028,整数拆分,典型的母函数入门。一个正整数可以拆分成多少种其他正整数和式的形式,例如4有5种形式。由于不考虑加数的顺序,因此这是一个组合问题。
     考虑n的拆分数,按照组合思维的考虑,实际上就是你选多少个1,选多少个2,选多少个3,…,最后保证选出的所有数和为n,问这种选法一共有多少种。所有n的拆分数形成一个数列,就对应上文中的{ai}。注意到n是和,对应的项为x的n次方,因此n出现在母函数的项的幂中。这一点有助于你理解下列乘式的产生。
      乘式如何确定呢?很简单,考虑1,选1个1就是1,选2个1就是2,选3个1就是3,…,因此1对应的乘式为
                                                (1+x+x^2+x^3+…)
Note,如果1个都不选就是0,也就是x的0次方,因此前面有一个单独的1。
       对于2而言,选0个就是0,选1个就是2,选2个就是4,…,因此2的乘式为
                                                 (1+x^2+x^4+…)
依次类推…然后把上述乘式全部乘出来,x^n的系数就是n的拆分数。
        下面来考虑程序实现,毫无疑问要用到多项式的乘法,可以直接写一个类或者针对结构体的函数来计算这种乘法。不过,你可以看到组合型母函数的乘式基本上系数都是1,否则就是0,所以不必特别封装一个多项式乘法,可以直接写成。
        Look hdu2082 http://acm.hdu.edu.cn/showproblem.php?pid=2082,给每一个字母赋一个值,A为1…Z为26等等。因此单词ACM的值就是18,同时题目指出不考虑字母顺序,也就是ACM和CAM是同一个单词。现在要求值不大于50的单词个数。本质上与上一道题一样,其实就是求幂不超过50的项的系数之和。Note,x的0次方不应计算在内。源代码如下:
 

#include <cstdio>
#include <algorithm>
#include <numeric>
using namespace std;
 
int A[26];
int G[51];//多项式系数
int T[51];//临时变量

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        for(int i=0;i<26;++i)scanf("%d",A+i);
        
        //首先将G设置为第一个式子,也就是字母A的
        fill(G,G+A[0]+1,1);
        fill(G+A[0]+1,G+51,0);
        fill(T,T+51,0);
        
        //连乘,考虑到后面的式子系数要么为1要么为0
        //因此不必写出正规的多项式乘法
        for(int i=1;i<26;++i){
            for(int j=0;j<51;++j)
                for(int k=0;k<=A[i]*(i+1)&&k<51;k+=i+1)
                    if ( j + k < 51 )//只计算50次及以下的
                        T[j+k] += G[j];
         
            //将临时变量的值拷贝到G
            copy(T,T+51,G);
            //清零
            fill(T,T+51,0);
        }
  
        //输出,因为0次代表没有字母,也就不算一个单词
        printf("%d\n",accumulate(G+1,G+51,0));
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值