母函数

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

母函数
母函数思想的起源可以追溯到18世纪Jacob B的《猜度术》一书。

这本书是在作者去世8年后的1713年出版的,它是早期概率论中最重要的著作。

《猜度术》一书共分四个部分,其中在第二部分中,作者讨论了组合论问题。

主要是运用伯努利数通过完全归纳法证明了n 为正整数时的二项式定理。

在第三部分中,作者把排列和组合的理论运用到概率论中,给出了24种有关在各种赌博情形中利益预测的例子。

在第四部分中作者给出了著名的伯努利大数定律:若P是事件发生一次的概率,q是该事件不发生的概率,则在n次实验中该事件至
少出现m次的概率等于的展开式中从项到包括为止的各项之和。

母函数是组合数学的一个重要理论。

Jacob B考虑掷n粒骰子时所得点数总和等于m,这种场合的数目等于
的展开式中这一项的系数,开了母函数研究的先河。

在18世纪,Euler L对组合方法的发
展做出了重大贡献。

他关于自然数的分解与合成的研究为母函数方法奠定了基础。

1812年,法国数学家Laplace P.S. 出版了《概率的分析理论》一书。

这本书第一部分的小标题为“母函数的计算”,这一部分致力于母函数计算的数学方法及其一般数学理论,这是对Euler L所提出的母函数理论的发展。

所以现代学术界认为母函数方法是由Euler L和Laplace P.S. 共同发现的。

由此,组合数学中的母函数理论基本建立起来了。

在当代组合学理论中,母函数是解决计数问题的重要方法。

一方面,母函数可以看成是代数对象,其形式上的处理使得人们可以通过代数手段计算一个问题的可能性的数目;另一个方面,母函数是无限可微分函数的Taylor级数。

如果能够找到函数和它的Talor级数,那么Taylor级数的系数则给出了问题的解。

本章主要介绍母函数的两种形式:普通型母函数和指数型母函数。

然后通过一些典型问题的分析,帮助读者加深对这一方法的理解。

并且在分析中,有的问题采用多种方法求解。

通过对比,读者可以明显地看到用母函数的方法解决问题具有较高的效率,并且程序具有非常规范的形式,易于实现。

在本章的最后,我们就组合计数的常用方法加以归纳,并用母函数的方法来描述SAT问题和顶点覆盖问题,以帮助读者更好地理解母函数的方法。

1普通型母函数
大家对图1所示的三角形一定不会感到陌生,这个三角形就是通常所说的杨辉下角形,在西方则称之为帕斯卡三角形。

世界上最早研究帕斯卡二角形的是中国的贾宪。

贾宪生活在大约11世纪上半叶。

他发展了中国古代数学的算法理论,在算法的抽象化、程序化,一般化方面做出了极大贡献。

贾宪总结了《九章算术》以来的开方程序,并创造了“开方作法本源”。

在《释锁》一书中,贾宪将0~6次的二项式展开式的系数,从上而下排成三角形,并提出了增乘方求廉法作为造表法,这是世界上最早的帕斯卡三角形。

继贾宪后,在杨辉(1261)、朱世杰(1303)、吴敬(1450)的著作中,都有关于帕斯卡三角形的研究。

在欧洲,15世纪的阿拉伯数学家Al-KashiG于1427年在《算术之钥》一书中给出了一个二项式展开式的系数表,这是欧洲对帕斯卡三角形的最早研究;而法国数学家Pascal B则于1654年在《算术三角形》中给出了如图2所示的三角形,但没有给出证明。

在杨辉三角形中的第n行的数字就是的展开式从低项到高项的各项系数。

其实,它的展开式就是一个母函数。

我们首先从分析开始来学习母函数。

首先分析(1+x)的物理意义。

它和选择物品的情形联系了起来。

在构造和分析一个母函数时,“1”一般都看做,虽然,但是比1具有更为丰富的物理意义。

这样,可对应于从n个物品中选取物品的情况。

在这n个物品中,如果没有选取第i () 个物品,则相当于从第i个括号中取出了;如果选取了第i个物品,则相当于从第i个括号中取出了;由于对于每一件物品而言,“选”与“不选”这两个事件是相互排斥的,也就是说,不可能同时做这两件事情,也不可能这两件事情都不
做。

所以每一个括号写为1 +x,这是加法原理的应用。

再来分析。

(1+x)表示对于每一件物品,都有“选”与“不选”两种可能性。


对于总共的n个物品而言,都要做出这样的选择,也就是说,对于每一件物品,都要决定“选择它”或者是“不选择它”,所以在这个时候就需要用到乘法原理了。

也就是把n个(1+x)乘
起来,就得到了。

这样在一个具体的选择中,如果没有选择第i个物品,则相当于从第i个括号中取出了;如果选择了第i个物品,则相当于从第i个括号中取出了。

这样,在的展开式前面的系数就是从n个物品中选取i个物品的所有组合情况的总数。

即的展开式
中前面的系数就是从n个物品选取i个物品的所有组合情况的总数。

下面我们给出普通型母函数的定义。

定义1给定数列,构造一函数
称F(x)为数列的母函数,其中序列只作为标志用,所以称之为标志函数。

标志函数的最有用和最重要的形式是。

在这种情况下,对于数列
的母函数为
这是我们研究的重点。

普通型母函数用下面的定理描述。

定理1设从n元集合中取k元素的组合是,若限定元素出现的次数集合为,则该组合数序列的母函数为
下面举几个简单的例子帮助读者理解普通型母函数的概念。

关于普通型母函数在程序设计竞赛中的具体应用读者可在本章后面几节中看到。

例1有重量为1, 3, 5(克)的砝码各两个,问:
(1)可以称出多少种不同重量的物品?
(2)若要称出重量为7(克)的物品,所使用的砝码有多少种本质不同的情况?
解根据定理1,构造母函数如下
其中,第一个括号中的1表示可以不用重量为1的砝码,x表示可以使用1个重量为1的砝码,表示可以使用2个重量为1的砝码。

同理,第二个括号中的1表示可以不用重量为3的砝码,表示可以使用1个重量为3的砝码,表示可以使用2个重量为3的砝
码,以此类推。

我们将上面的多项式展开后,比如多项式扩,它表示我们可以用2个重量为1的砝码和1个重量为5的砝码来称出重量为7的物体,代表了一种称
重量的方法。

显然当把G(x)展开并将指数相同的项进行合并后有多少个系数非0的项,就代表了可以称量出多少种不同重量的物品,而相应的前而的系数则表明了称量出重量为i的物品可选砝码的方案数。

所以,项前面的系数就是本题第二问的解,而G(x)的展开式中系数不为0的项的个数就是我们所求的方案数。

例2投掷一次色子,出现点数的概率为1/6。

问连续投掷10次,出现的点数之
和为30的概率是多少?
解用多项式
表示投掷一次可能出现的点数,则连续投掷10次,其和为30的方法总数为
中的系数,而
所以的系数为
=2930455
故其所求概率为
例3求多重集合的每个至少出现一次的K-组合数。

解依题意,可构造母函数
所以可得
2指数型母函数
在1节中介绍的普通型母函数用来求解多重集的组合问题,而解决多重集的排列问题时,则需要引入另外一种形式的母函数,这就是在本节中所要介绍的指数型母函数。

指数型母函数由下面的定理来加以描述。

定理2从多重集合中选取k个元素的k- 排列中,若限定元素出现的次数集合为,则该组合数序列的母函数为
在应用指数母函数解题时,我们经常需要应用的Taylor展开式,即
对此稍加变换便可得到
关于级数的有关知识可以参考附录。

比较普通型母函数和指数型母函数,可以看到普通型母函数的标志函数为
而指数型母函数的标志函数为
关于指数型母函数的标志函数的物理意义可以这样来理解。

对于
表示在一个方案中某个元素出现了j次,而在不同位置中的j次出现是相同的,所以在计算排列总数时,只应算作一次。

由第3章的知识可以知道,最后的结果应该除以j! 。

在应用指数型母函数时,还应该特别注意,对于一个指数型母函数的展开式
我们应将化为这样的形式
在指数型母函数中,只有才是我们所需要的结果。

读者在下面两个例题中可
以体会出这一点。

例4 设有六个数字,其中三个数字1,两个数字5,一个数字8。

问能组成多少个四位数?
解这实际上是求中取四个的多重集排列数问题。

其指数型母函
数为
所以可以组成38个四位数。

例5多重集合的k- 排列数序列的指数型母函数为
我们发现在这道题目中运用指数型母函数得到的结果与直接用排列组合的知识求得的结果是一样的。

例6由1,2,3,4这四个数字能组成多少个五位数?要求这五位数中1出现两次或三次,2最多出现一次,4出现偶数次。

解1出现两次或三次,对应的母函数为
2最多出现一次,对应的母函数为
3的出现次数不做要求,对应的母函数为
4出现偶数次,对应的母函数为
所以可得母函数如下
所以满足题意的五位数有
3质数分解问题
3.1问题描述
任何大于1的自然数n都可以写成若干个大于等于2且小于等于n的质数之和的表达式(包括只有一个数构成的质数之和的表达式的情况),并且可能有不止一种质数和的形式。

例如,9的质数和表达式就有四种本质不同的形式:9=2+5+2=2+3+2+2=3+3+3=2+7 这里所谓两个本质相同的表达式是指可以通过交换其中一个表达式中参加和运算的各个数的位置而直接得到另一个表达式。

试编程求解自然数可以写成多少种本质不同的质数和表达式。

3.2试题分析
构造普通型母函数如下
在第一个括号中,的指数都为2的倍数,其中“1”可以看做;在第二个括号中,的指数都为3的倍数,在第三个括号中,的指数都是5的倍数,……,在任意一个括号中,的指数一定都为某一质数的倍数,将多项式展开后,前面的系数便是我们所求的方案数。

例如,对于题中所给出的式子:9=2+5+2=2+3+2+2=3+3+3=2+7,我们可写成指数的形式
上面式子的第一行表示,在第一个括号中提出,在第二个括号中提出1,在第三个括号中提出。

这样便得到了。

对于第二、三、四行的意义也如此。

下面给出参考程序代码。

参考程序1:example3
programs examples 3;
const
infile=’input.txt’;
outfile=’output.txt’;
var
dada,s:array[0..100] of longint; {data记录展开多项式的系数}
info:array[1..46] of integer; {数组info记录200以内的所有质数}
l,n:integer;
ans:longint;
procedure makeinfo; {产生200以内的所有质数}
var
i,j:integer;
check:Boolean;
begin
info[1]=2;
l:=1; {l表示质数的个数}
for i:=2 to 200 do
begin
Check:=true;
for j:=2 to trunc(Sqrt(i))+1 do
if (i mod j=0) then
begin
check:=false;
break;
end;
if check then {当i是质数时进入数组} begin
inc(l);
info[l]:=i;
end;
end;
end;
procedure done;
var
I,j,k:longint;
begin
fillchar(data,sizeof(data),0); {初始化操作}
for k:=0 to trunc(n/2)do data[k*2]:=1;
for j:=2 to 46 do {将多项式展开}
begin
fillchar(s,sizeof(s),0);
if info[j]>n then break;
for i:= 0 to n-1 do
if data[i]>0 then
for k:=1 to trunc((n-i)/info[j])+1 do inc(s[i+info[j]*k],data[i]);
for i:=0 to n do inc(data[i],s[i]);
end;
end;
procedure main; {主程序}
var
fl,f2:text;
begin
assign(f1,infile);
reset(f1);
assign(f2,outfile);
rewrite(f2);
while not (eof(f1)) do
begin
readln(f1,n);
ans:=0;
done;
writeln(f2,data[n]);
end;
close(f1);
close(f2);
end;
begin
makeinfo;
main;
end.
4“红色病毒”问题
4.1 问题描述
最近医学研究者发现了一种新病毒,因为其蔓延速度与最近在Internet上传播的“红色代码”不相上下,故被称为“红色病毒”。

经研究发现,该病毒及其变种的DNA的一条单链中,胞嘧啶、腺嘧啶均是成对出现的。

这虽然是一个重大发现,但还不是该病毒的最主要特征,因为这个特征实在太弱。

为了搞清楚该病毒的特征,软件工程师Grant对此产生了兴趣。

他想知道在这个特征下,可能成为病毒的DNA序列的个数。

更精确地说,Grant需要统计所有满足下列条件的长度为n的字符串的个数:
(1)字符串仅由A、T、C、G四个英文字母组成。

(2)A出现偶数次(也可以不出现)。

(3)C出现偶数次(也可以不出现)。

当时,所有满足条件的字符串有如下六个:TT、TG、GT、GG、AA、CC。

由于这个数可能非常庞大,你只需给出最后两位数字即可。

4 .2试题分析
这道题目的解法有许多种,在此讨论本题的母函数解法。

由于字符串由A、T、C、G四个英文字母组成,且每个字母的出现次数均可多于一次,所以此题可应用指数型母函数求解。

设是满足条件的长度为n的字符串个数,则对于数列的指数型母函数为
其中第一个括号表示A、C只能出现偶数次(包括不出现的情况),第二个括号表示T、G可以出现任意多次,化简原式得
所以有
即为满足条件的、长度为的字符串的个数目
令,则就是本题的解。

因为题目中输入数据的范围是,所以需要对进行化简。

对于,当时,
当时,
又当时,且
对于,20是它的一个周期。

问题分析到这里便得到了解决。

参考程序2 : example4
Program example4
const
infile=’input.txt’;
outfile=’output.txt’;
var
a1,a2:extended;
ans:array[1..24] of integer;
I,n,j:longint;
f1,f2:text;
begin
assign(f1,infile);
assign(f2,outfile);
reset(f1);
rewrite(f2);
for j : =1 to 24 do {产生n=1 , 2时的特殊情况和一个周期的结果} begin
a1:=1;
a2:=1;
for i:=1 to j-1 do
begin
a1:=trunc(a1*2) mod 100;
a2:=trunc(a2*4) mod 100;
end;
ans[j]:=trunc(a1+a2) mod 100;
end;
readln(f1,n);
while (n<>0) do
beg in
if n<4 then j:=n
eise begin
j:=(trunc(n) mod 20);
if j <4 then j:=j+20;
end;
writeln (f2,ans[j]);
readln(f1,n);
end;
close(f1);
close(f2);
end.
5“自共轭Ferrers图”问题
5.1 问题描述
整数的拆分问题是组合数学中的一个基本问题。

一个整数的拆分是指把这个整数分成若干个与次序无关的正整数之和。

在人们研究整数拆分问题时,为了直观地帮助人们的思维,
可以用Ferrers图来表示。

Ferrers图是一个自上而下的层格子,且上层格子数不少于下层格子数,如图3所示,图中的虚线称为Ferrers图的虚轴。

若将图3绕虚轴旋转,即将第一行与第一列对调,将第二行与第二列对调,……,这样所得到的图仍为Ferrers图,如图4所示。

这两个图称为一对共扼Ferrers图。

有一些Ferrers图,沿虚轴转换后
的Ferrers图仍为它本身,也就是说这个Ferrers图关于虚轴对称,那么这
个Ferrers图称为自共轭Ferrers图。

图5便是一个自共扼Ferrers图。

在组合数学中,一个含有个格了的Ferrers图对应一个数的拆分。


本题中,我们称含有个格子的Ferrers图的大小为。

例如把6拆分成三
个整数3,2,1之和,即6=3+2+1,对应的Ferrers图的第一行有3个格子,
第二行有2个格子,第三行有1个格子,如图5所示。

这个Ferrers图的
大小为6。

由于一个整数拆分成若干个整数之和的拆分方法是不惟一的,所以
说一个大小为的Ferrers图的数量也是不惟一的。

现在需要寻找的是大小为的自共扼
Ferrers图的总数。

5.2 试题分析
组合数学中的Ferrers图是组合计数的重要工具,对自共扼Ferrers图的深入理解是解决这道题目的出发点。

在组合数学中,关于自共扼Ferrers图有下面一个性质:整数的Ferrers
图为自共轨的数量与整数n拆分成若干个互异的奇数的拆分数相等。

由此可以推出一个结论:若一个整数的Ferrers图是自共扼的,则这个整数必定能拆分成若干个互异的奇数,这是解题的关键。

这一结论是不难理解的。

因为一个Ferrers图若为自共扼的,那么这个图第一行的格子数和第一列的格子数一定是相等的,由于它们共用左上角的一个格子,所以这些格子数量之和一定为奇数;若去掉第一行和第一列后,剩下的第一行的格子数和第二列的格子数
也一定是相等的;若去掉第行和第列后,剩下的第行和第列的格子数也是相等的。

所以说一定可以把n拆分成若干个奇数的和。

由于ferrers图中第层中共有格子数不能
多于第层格子数,所以这些奇数一定互异。

一个大小为6的自共扼Ferrers图用这样的方法拆分得到的图形如图5.6所示,对应的表达式为6=5+1。

1.算法1:用递推法求解
用表示大小为且第一行和第一列均有个格子的自共扼Ferrers图的总数,令
我们所要求解的方法总数为
其中,且。

经过分析可知
其中,且
初始条件为
有了上面两个递推公式,便不难求解此题了。

参考程序3: examples5_1
program example5_1;
type
a=array [ 1..200] of lonqint;
const
infile=’ferrers.in’;
outfile=’ferrers .out’;
var:integer;
ans:longint;
d:array [1..400] of of ^a;
procedure init;
var
f:text;
x:integer;
begin
assign(f,infile);
reset(f);
readln (f, n); {读入数据}
for x:=1 to n do {申请动态存储空间,并进行初始化操作} begin
new(d[x]);
fillchar(d[x]^,sizeof(d[x]^),0);
end;
close(f);
end;
procedure done; {运用上面建立的递推关系求解本题}
var
i,j,k,l,x:longint;
begin
d[1]^[1]:=1;
i:=1:
while (i<n) do
begin
i:=i+2;
k:=(i+1) div 2;
d[i]^[k]:=1;
end;
for i:=2 to n do
begin
k:=(i+1) div 2;
for j:=2 to k do
begin
l:=0;
repeat
inc(l);
until (l*l>=i-2*j+l) and (l*l<i-2*j+2*l);
for x:=1 to j-1 do d[i]^[j]:=d[i]^[j]+d[i-2*j+l]^[x];
end;
end;
l:=0;
repeat
inc(l);
until (l*l>=n) and(l*l-2*j+l<n);
x:=(n+1)div 2;
for i:=1 to x do ans:=ans+d[n]^[i];
end;
procedure out; {输出结果}
var
f:text;
begin
assign(f,outfile);
rewrite(f);
if ans>0 then writeln(f,ans)
else writeln(f,’NO ANSWER’);
close(f);
end;
begin
init;
done;
out;
end.
2. 算法2:用母函数求解
是否有更好一些的算法呢?在上面的分析中,我们发现:若一个整数的Ferrers图是自共轭的,则这个整数必定能拆分成若干个互异的奇数。

反之,若干个互不相同的奇数必对应于一个自共扼Ferrers图。

于是这个问题的解就是母函数
的展开式中项的系数,若项的系数为0,则说明不存在大小为的自共轭Ferrers图。

参考程序4:examples5_2
program example5_2;
const
infile=’ferrers.in’;
outfile=’ferrers.out’;
var
n:integer;
ans:longint;
procedure init;读入数据}
var
f:text;
begin
assign(f,infile);
readln(f,n);
close(f);
end;
procedure out; {输出结果}
var
f:text;
begin
assign(f,outfile);
rewrite(f):
if ans>0 then writeln(f,ans)
else writeln(f,’NO ANSWER’);
close(f);
end;
procedure done; {计算中项的系数}
var
i,j,k:longint;
data,s:array [0..300] of longint;
begin
fillchar(data,sizeof(data),0);
data[0]:=1;
dada[1]:=1;
for j:=2 to trunc(2*n-1) do
begin
fillchar(s,sizeof(s),0);
for i:=0 to n d0
if data[i]>0 then begin
if (i+2*j-l<=n) then inc(s[i+2*j-1],data[i]);
inc(s[i],data[i]);
end;
for i:=0 to n do data[i]:=s[i];
end;
ans:=data[n];
end;
begin
init;
done;
out;
end.
3.两种算法的比较
对这两个程序运行的时间进行比较,我们得到表5.1所示的结果。

比较这两个算法所使用的空间。

算法1的空间复杂度为。

算法2的空间复杂
度为。

由此可见,运用母函数的原理设计的算法,无论是在时间上还是空间上,
都要比普通的递推算法高效得多。

实际上,算法2参考程序4在空间上还有进一步优化的余地。

首先考察我们在前面给出的母函数
程序的执行过程就是将这个多项式展开并求得项的系数。

假设现在已经将前p个括号展
开,得到展开后的多项式为
第p+1个括号中的多项式为,则将前p+1个括号展开后的结果为

所以在计算时,可将已经得到的多项式中的每一项(按照的递减顺序)直接乘以,即执行操作。

经过修改后的done子过程代码如下:
procedure done;
var
i,j,k:longint;
data:array[0..300] of longint;
begin
fillchar(data,sizeof(data),0);
data[0]:=1;
data[1]:=1;
for j:=2 to trunc(2*n-1) do
for i:=n downto 0 do
if data[i]>0 then
if (i+2*j-1<=n) then data[i+2*j-1]:=data[i+2*j-1]+data[i];
ans:=data[n];
end;
算法2经过这样的修改后,空间复杂度降为,时间复杂度与原来的算法相同。

相关文档
最新文档