母函数是数学中的一个概念,又称为生成函数,是计数方面的一个重要理论和工具。
母函数分为普通型母函数和指数型母函数,前者用于解决多重集的组合问题,后者用于解决多重集的排列问题。
多重集可以理解为同一个元素可以出现多次的集合
简单来说:
对于一个序列 a 0 , a 1 , a 2 , ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ a n a_0,a_1,a_2,\cdot\cdot\cdot\cdot\cdot\cdot a_n a0,a1,a2,⋅⋅⋅⋅⋅⋅an,我们称 a 0 a_0 a0+ a 1 x a_1x a1x+ a 2 x 2 a_2x^2 a2x2 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ + a n x n \cdot\cdot\cdot\cdot\cdot\cdot+a_nx^n ⋅⋅⋅⋅⋅⋅+anxn为该序列的普通型母函数
给定一个数N,问N有几种划分方式。
比如N=4,则有:
4 = 4;
4 = 3 + 1;
4 = 2 + 2;
4 = 2 + 1 + 1;
4 = 1 + 1 + 1 +1;
共计5种划分方式,注意1+3与3+1是同一种划分方式。
换种问法就是换零钱的方案数,砝码称重的方案数。
其实这些问题本质上就是多重集的组合问题。
模型:
给定n种物品及每种物品的数量(可以为无限个)和权值,问你权值和为X的组合方案有几种?
在砝码称重问题中,这个权值就是一种砝码的质量
在换零钱问题中,这个权值就是一种硬币的币值
在整数划分问题中,这个权值就是数的数值大小。
从模型的角度分析整数拆分问题就是:
给你n个数(1~N),每个数的权值就是其本身数值大小,每个数可以被重复取用任意次,问你数值和为N的组合方案有几种?
先看三个等式:
x 4 ⋅ x 5 = x 9 x^4\cdot x^5 = x^9 x4⋅x5=x9
x 3 ⋅ x 6 = x 9 x^3\cdot x^6 = x^9 x3⋅x6=x9
x 3 ⋅ x 3 ⋅ x 3 = x 9 x^3\cdot x^3 \cdot x^3 = x^9 x3⋅x3⋅x3=x9
观察它们的指数,很轻易的就能发现上述三个等式其实暗含了三种9的划分方式(组合方案)。
用x的指数来代表权值,则:
组合数的加法化为了幂级数的乘幂相加
这句话很重要,不懂可以之后再理解。
更进一步:
给定一个4g和一个5g的砝码,问你能称重的范围及对于每一个重量称重的方案数
答案很简单,我们可以称量4g,5g,9g重量的待测物体且各自只有一个方案。
但我们也可以利用之前发现的规律来解决:
( 1 + x 4 ) ⋅ ( 1 + x 5 ) = 1 + x 4 + x 5 + x 9 (1+x^4)\cdot(1+x^5)=1+x^4+x^5+x^9 (1+x4)⋅(1+x5)=1+x4+x5+x9
首先1代表 x 0 x^0 x0,一个0g的砝码。
一个括号的内容就是一个多项式,一个多项式对应一种砝码,多项式中的每一个项代表了一种使用的情况。比如对于第一个多项式1就代表不使用这个砝码, x 4 x^4 x4代表使用。
两个多项式各含两项,相乘就得到了四个项:
1由两个1相乘而来,代表两个砝码都不使用,自然就只能称量0g的物体
x 4 x^4 x4由 x 4 x^4 x4和1相乘而来,代表使用了4g的砝码,而5g的不使用
x 5 x^5 x5由 x 5 x^5 x5和1相乘而来,代表使用了5g的砝码,而4g的不使用x 9 x^9 x9由 x 4 x^4 x4和 x 5 x^5 x5相乘而来,代表两个砝码都使用,称量9g的物体
这样我们就得到了用一个4g和一个5g的砝码组合后的所有方案
推而广之:
我们以一个多项式代表一种砝码,多项式中的每一项代表这个砝码具体的使用情况。
对于更复杂的问题,比如有2个1g的砝码和3个2g的砝码和1个4g的砝码。
( 1 + x 1 + x 2 ) ⋅ ( 1 + x 2 + x 4 + x 6 ) ⋅ ( 1 + x 4 ) (1+x^1+x^2)\cdot(1+x^2+x^4+x^6)\cdot(1+x^4) (1+x1+x2)⋅(1+x2+x4+x6)⋅(1+x4)
第一个多项式对应的是1g的砝码:
如果不使用该法码,我们就用 x 0 x^0 x0来表示
如果使用一次该砝码,我们就用 x 1 x^1 x1来表示
如果使用两次该砝码,我们就用 x 2 x^2 x2来表示,可以发现它和使用一次2g的砝码是一样的,因为它们都称量2g的物体。
其实上述过程就是构造母函数的过程。
结合多项式相乘的过程,结果的每一项都是在各个多项式中各自取一项相乘而来,它和我们取砝码的过程是对应的。例:
对于第一个多项式(1g的砝码),我们取 x 1 x^1 x1表示使用一个该砝码
对于第二个多项式(2g的砝码),我们取 x 2 x^2 x2表示使用一个该砝码
对于第三个多项式(4g的砝码),我们取 x 4 x^4 x4表示使用一个该砝码
那么结果就是 x 1 ⋅ x 2 ⋅ x 4 = x 7 x^1\cdot x^2\cdot x^4 =x^7 x1⋅x2⋅x4=x7代表我们可以将其用于称量7g的物体,这样我们就得到了一种7g的方案,其实还有一种7g的方案就是 x 1 ⋅ x 6 ⋅ x 0 x^1\cdot x^6 \cdot x^0 x1⋅x6⋅x0。
求完结果之后合并同类项,这样我们就得到了:
1 + x + 2 x 2 + x 3 + 3 x 4 + 2 x 5 + 4 x 6 + 2 x 7 + 3 x 8 + x 9 + 2 x 10 + x 11 + x 12 1+x+2x^2+x^3+3x^4+2x^5+4x^6+2x^7+3x^8+x^9+2x^{10}+x^{11}+x^{12} 1+x+2x2+x3+3x4+2x5+4x6+2x7+3x8+x9+2x10+x11+x12
每一项的指数就代表了砝码组合后的重量;
可以看到 x 7 x^7 x7前面的系数为2,这两个 x 7 x^7 x7分别是通过 x 1 ⋅ x 2 ⋅ x 4 x^1\cdot x^2\cdot x^4 x1⋅x2⋅x4和 x 1 ⋅ x 6 ⋅ x 0 x^1\cdot x^6 \cdot x^0 x1⋅x6⋅x0得到的。也就是说某一项前面的系数就代表了方案的个数。
由上述过程我们就得到了解决多重集组合问题的一种方法:
①构造母函数
②展开,合并同类项,就得到了答案
理解了之后,还有一个大问题就是如何应用上述理论,将其通过代码实现出来。
对于一个给定的数N,我们可以通过构造下面这个母函数求其划分方案数:
( 1 + x + x 2 + x 3 + ⋅ ⋅ ⋅ + x N ) ⋅ ( 1 + x 2 + x 4 + x 6 + ⋅ ⋅ ⋅ ) ⋅ ( 1 + x 3 + x 6 + x 9 + ⋅ ⋅ ⋅ ) ⋅ ⋅ ⋅ ( 1 + x N ) (1+x+x^2+x^3+\cdot\cdot\cdot+x^N)\cdot(1+x^2+x^4+x^6+\cdot\cdot\cdot)\cdot(1+x^3+x^6+x^9+\cdot\cdot\cdot)\cdot\cdot\cdot(1+x^N) (1+x+x2+x3+⋅⋅⋅+xN)⋅(1+x2+x4+x6+⋅⋅⋅)⋅(1+x3+x6+x9+⋅⋅⋅)⋅⋅⋅(1+xN)
类比于砝码:第一个多项式对应数字1,第二个多项式对应数字2……
我们要求的方案数就是x^N的系数
模拟多项式相乘并合并同类项的过程:
c数组存放之前几个多项式相乘的结果,下标i表示某一项的指数,存放的值表示系数(系数的英文:coefficient)
temp是每次多项式相乘的计算结果
c数组初始化为1,存放的其实是第一个多项式(系数全为1)
long long c[150],temp[150];
for(int i=0;i<=N;i++) c[i]=1;
memset(temp,0,sizeof(temp));
核心部分就是一个三重循环:
for(int i=2;i<=N;i++){
for(int j=0;j<=N;j++)
for(int k=0;k+j<=N;k+=i)
temp[k+j]+=c[j];
for(int j=0;j<=N;j++)
c[j]=temp[j],temp[j]=0;
}
解释:
大致流程:
让第一个多项式和第二个多项式相乘,将结果存在c数组中,这样c数组存放的就是前两个多项式相乘的结果。
再让第三个多项式和c数组相乘,得到前三个多项式相乘的结果。
以此类推……
temp数组则是计算用的,每次计算都要初始化为0;
最外层循环:循环变量i表示的是当前运算到了第几个多项式,因为第一个多项式的答案已经存在了c数组中,所以i从2开始。
第二层循环:循环变量j表示的是当前运算到了c数组的第几项(c数组存放的是前几个多项式相乘的结果)
第三层循环:循环变量k表示的是当前运算的这个多项式运算到的项的指数(第i个多项式指数每次都是加i的,所以k+=i)
再通俗一个的解释:每次模拟的都是两个多项式的相乘,前一个多项式是前几个多项式相乘的结果,后一个多项式是新参与相乘的多项式。
为什么是temp【k+j】+=c【j】?
①由上述解释可以发现k和j是两个多项式各自某一项的指数,它们相乘的话对应的结果自然放在k+j上(项的相乘体现为指数的相加);
②至于为什么+=c【j】,因为当前这个多项式必然系数都是1,所以相乘后的系数就由c【j】决定(前一个多项式的系数)
③加法是因为合并同类项用的是加法。
总结一下就是:
三重循环,一个控制运算到了第几个多项式,另外两个各控制一个多项式的指数,每次乘完之后都要把c数组更新为目前的结果,temp数组重新赋0;
完整模板:
#include
#include
using namespace std;
int main()
{
long long N;
long long c[150],temp[150];
while(~scanf("%lld",&N)){
for(int i=0;i<=N;i++)
c[i]=1;
memset(temp,0,sizeof(temp));
for(int i=2;i<=N;i++){
for(int j=0;j<=N;j++)
for(int k=0;k+j<=N;k+=i)
temp[k+j]+=c[j];
for(int j=0;j<=N;j++)
c[j]=temp[j],temp[j]=0;
}
printf("%lld\n",c[N]);
}
return 0;
}
A - Ignatius and the Princess III
纯模板,套个板子就过了
B - Square Coins
换零钱方案数问题, 需要理解构造母函数和多项式相乘的过程
C - Holding Bin-Laden Captive!
需要理解原理,点此处跳转至题解:
F - 找单词
需要理解原理