组合数学

Published: by Creative Commons Licence

  • Tags:

几类组合数加总的计算方式

类型1

对于所有k=0,1,2,,n,要求计算:

f(k)=ni=0ci(ki)

很显然存在O(n2)的算法,但是这太慢了,下面我们利用多项式进行加速计算。

f(k)=ni=0ci(ki)=ni=0cik!i!(ki)!=k!ni=0cii!1(ki)!

其中ni=0cii!1(ki)!可以表示为下面两个函数的卷积

A(x)=ni=0cii!xiB(x)=ni=01i!xi

利用快速卷积可以在O(nlog2n)的时间复杂度内实现。记C(x)=A(x)×B(x),那么f(k)=k!Ck

类型2

对于所有k=0,1,2,,n,要求计算:

f(k)=ni=0ci(ik)

很显然存在O(n2)的算法,但是这太慢了,下面我们利用多项式进行加速计算。

f(k)=ni=0ci(ik)=ni=0cii!k!(ik)!=1k!ni=0cii!1(ik)!

其中右边部分ni=0cii!1(ik)!是等差卷积,即令

A(x)=ni=0cni(ni)!xiB(x)=ni=0i!xi

C(x)=A(x)×B(x)

那么有f(k)=1k!Cnk

利用快速卷积算法我们可以在O(nlog2n)的时间复杂度内计算出所有结果。

类型3

统计:

ai=0bj=0(ni)(nij)

其中a+b+1n

先来考虑其组合意义,这个问题等价于,有一个序列,取值范围为0~2。我们希望0出现的次数不会超过a,1出现的次数不会超过b。问有多少这样的序列。

我们可以用容斥来解决,问题变成四个:

  • 总共有多少个序列(3n)
  • 0出现a次以上的序列数
  • 1出现b次以上的序列数
  • 0、1各出现a、b次以上的次数(0)

因此我们只需要解决第二个和第三个问题,比如第二个问题:

ni=a+1(ni)2ni

这只是一趟循环,第三个问题同。整个时间复杂度为O(n)

如果不能保证a+b+1n,那么我们可以将问题现在转换为类型1,之后求解,时间复杂度为O(nlog2n)

非连续0问题

形式1:对于一个包含k个1的二进制序列,如果没有任意两个0相邻,且第一个数一定是0,那么称该序列为良好的。问,对于所有长度为n的二进制序列,其中有多少是良好的。

这个问题不是很难,我们可以直接用动态规划进行解决。但是除了动态规划,组合数学能提供一种更加简单快捷的解决方案。

这个问题实际上等价于,有两种子序列:

  • 10
  • 1

我们希望用k个上面子序列来,来组成一个长度为n-1的二进制序列。通过方程组可以简单地推出10序列出现的次数a和1出现的次数b。

{a+b=k2a+b=n1

之后我们进行多重排列的计算就可以得出总的数目了,结果是k!a!b!

下面我们修改一下原来问题:

形式2:对于一个包含k个1的二进制序列,如果没有任意两个0相邻,且第一个数一定是1,那么称该序列为良好的。问,对于所有长度为n的二进制序列,其中有多少是良好的。

这个问题实际上比上一个问题还要简单,用同样的理论,只是方程组稍微变动一下:

{a+b=k2a+b=n

形式3:对于一个包含k个1的二进制序列,如果没有任意两个0相邻,且第n个数一定是1(0),那么称该序列为良好的。问,对于所有长度为n的二进制序列,其中有多少是良好的。

直接翻转一下二进制序列,计算完成后翻转回去即可。

形式4:对于一个包含k个1的二进制序列,如果没有任意两个0相邻,且第1个数和第n个数一定是0,那么称该序列为良好的。问,对于所有长度为n的二进制序列,其中有多少是良好的。

先考虑第一个数为0的所有良好序列,其中一部分的尾部是0,其余的则是1。为0表示10子串进行结尾的,因此修改方程组即可。

{a+b=k12a+b=n3

统计选球方案

问题1:有n个球,m种颜色。其中第i种颜色的球有ai个。我们现在希望从所有球中挑选k个球,问有多少种选取方案。n,m105

考虑从n个球种挑选若干个球的方案。仅考虑颜色为i的球,其挑选方案的生成函数为fi(x)=xa1+xa11++x1+1。当有m种球时,颜色的生成函数为f(x)=mi=1fi(x)

我们可以用快速卷积+分治来计算,总的时间复杂度为O(nlog2mlog2n)

问题2:有m种颜色,每种颜色的球都有无数个,现在从中取出n个球(两种取法不同当且仅当存在某种颜色,该种颜色的球的数目在两种方案中不同),问有多少种取法。n,m105

xi表示取出的第i种颜色的球的数目,那么有x1+x2++xm=nxi0。取法的数目通过隔板法可以直接得出(m+n1n1)

问题3:有m种颜色,每种颜色的球都有无数个,现在从中取出n个球(两种取法不同当且仅当存在某种颜色,该种颜色的球的数目在两种方案中不同),问每种颜色的球都至少取出一个的前提下有多少种取法。

xi表示取出的第i种颜色的球的数目,那么有x1+x2++xm=nxi1。取法的数目通过隔板法可以直接得出(mn+n1n1)

问题4:有m种颜色,每种颜色的球都有无数个,现在从中取出n个球(两种取法不同当且仅当存在某种颜色,该种颜色的球的数目在两种方案中不同),问每种颜色的球取出数目不超过k的前提下有多少种取法。n,m105

这个可以用容斥进行解决。记录Ai表示第i种颜色出现不超过k次的所有方案的集合。那么结果为:

|A1A2An|=mi=0(1)i(m(k+1)i+n1n1)

问题5:有m种颜色,每种颜色的球有R个。现在从中取出n个球(两种取法不同当且仅当存在某种颜色,该种颜色的球的数目在两种方案中不同),要求每种颜色的球至少被取出L个,问有多少种取法。n,m105

首先我们可以消除左边界带来的影响,那么问题就变成了x1++xm=n,且xiR。这个可以用容斥来处理。我们定义Ci表示所有满足xi>R的方案组成的集合。那么问题的解就是:

|¯C1¯C2¯Cn|=mi=0(1)i(mi)(ni(R+1)+m1m1)

问题6:有m种颜色,每种颜色的球都有无数个,现在从中依次取出n个球(两种取法当且仅当两种取法得到的颜色序列不同),问有多少种取法。n,m105

这个问题非常容易,每次取的时候有m种可能,因此结果为mn

问题7:有m种颜色,每种颜色的球都有无数个,现在从中依次取出n个球(两种取法当且仅当两种取法得到的颜色序列不同),且要求每种颜色的球都至少被取出一个,问有多少种取法。n,m105

这个可以用容斥进行解决。记录Ai表示第i种颜色出现的所有方案的集合。那么结果为:

|A1A2An|=mi=0(1)i(mi)(mi)n

问题8:有m种颜色,每种颜色的球都有无数个,现在从中依次取出n个球(两种取法当且仅当两种取法得到的颜色序列不同),且要求第i种颜色的球出现的次数xi满足LixiRi,问有多少种取法。n,m500,LiRi500

用动态规划解决。记录dp(i,j)表示前i种颜色选择j个球的方案数。这里没有考虑到排列带来的影响,因此dp(i1,j)dp(i,j+t)的贡献应该为dp(i1,j)(j+tt)

这个问题还可以通过卷积来做,只是比较复杂一些。

统计所有和方案数问题

考虑有n个未知数x1,x2,,xn,其中每个未知数xi都可以取010之间的若干个数(不同未知数的可能的取值范围可能不同)。现在问,对于[0,10n]之间的任意数t,有多少种不同的未知数选择方案,可以使得所有未知数之和正好为t

这个问题是一个很明显的用生成函数来求解的问题。对于第i个未知数xi,我们建立一个多项式fi,如果xi可以取值k,那么我们就讲fixk项系数设置为1,否则设置为0。

可以发现ni=1fi(x)xs项系数表示的是有多少种不同方案,可以使得所有未知数之和为s

错位排序的一些问题

问题1:给定一个1n的排列P,要求计算有多少排列S满足对于所有1inPiSi。其中1n106

可以直接容斥解决。定义Ai表示满足Pi=Si的所有排列S组成的集合。我们要求的是:|¯S1¯Sn|

注意到SiSj表示的是所有已知Si=Pi,Sj=Pj的排列,其大小应该为(n2)!

直接代入公式可以得出:ni=0(ni)(1)i(ni)!,这就是要求的结果,提前预处理组合数,可以在O(n)时间复杂度内计算出结果。

问题2:给定一个1n的排列P,其中Pk个位置为空(表示这些位置不限制)。设g(n,k)表示有有多少排列S满足对于所有1inPiSi,要求输出g(0,0),g(1,0),g(1,1),,g(n,0),g(n,1),,g(n,n)。其中1n3000

这个问题可以动态规划解决。定义f(i,j)表示还需要放置i个错排,之前还有j个元素未放入(对应还有j个空槽)。那么考虑转移公式有哪些贡献。

考虑处理第i个位置,以及其禁止放置的元素Pi,那么有四种情况:

  1. Pi不会放入之前j个空槽中的任意一个,且之前的j个元素也不会放入第i个位置。
  2. Pi会放入之前j个空槽中的任意一个,但是之前的j个元素也不会放入第i个位置。
  3. Pi不会放入之前j个空槽中的任意一个,但是之前的j个元素中的一个会放入第i个位置。
  4. Pi会放入之前j个空槽中的任意一个,且之前的j个元素中的一个会放入第i个位置。

于是可以推出转移公式为f(i,j)=f(i1,j+1)+2jf(i1,j)+j2f(i1,j1)

接下来我们考虑怎么计算g(i,j),容易发现当j=0时,直接有g(i,0)=f(i,0)。接下来我们考虑其它情形。我们可以这样思考,现在我们有j个位置没有约束,ij个位置是存在约束的。我们可以将所有没有约束的位置按照位置的下标从小到大进行处理,因此如果j>0,那么我们可以选择最小下标的没有约束的位置,设置它的值,值可能是没有出现在P中的(对应的就是j种),也可能是出现在P中的(有ij)种,后者会将一个有约束的位置转换为没有约束的。因此转移为g(i,j)=jg(i,j1)+(ij)g(i1,j),特殊的有g(i,0)=f(i,0)

到此我们可以以O(n2)的时间复杂度实现算法。

递增序列方案数

题目1:现在有n个位置数a1,a2,,an,要求为这n个位置数赋值,并且满足LaiR,且序列严格递增,问有多少种不同的赋值方案(两个方案不同当且仅当存在一个未知数被赋予不同的值),其中n105,0LR109

这个问题不难解决,由于是严格递增,因此我们每次从m=RL+1个数中选择n个数后,这种选择方案正好对应一种有效的赋值方案。因此总的赋值方案为(mn)

题目2:现在有n个数a1,a2,,an,要求为这n个数赋值,并且满足LaiR,且序列非严格递增,问有多少种不同的赋值方案(两个方案不同当且仅当存在一个未知数被赋予不同的值),其中n105,0LR109

题目2不能用题目1的方式解决,因为存在重复计算。我们引入新的一些变量d1,d2,,dn,其中di=aiai1,特殊的d1=a1Lai序列上的非严格递增就变成了di序列非负,而ai序列的左右边界就变成了L+d1+d2++dnR。我们可以引入一个新的非负未知变量x,将不等式转换为等式d1+d2++dn+x=RL。每个di序列的赋值方案与ai序列的赋值方案正好一一对应,其方案数可以通过隔板法计算得到:(RL+nn)

其实这道题还有一种思路,就是记录ci表示i这个数在结果的序列中出现的次数,那么很显然有ci0Ri=Lci=n,即给出RL+1个非负未知数,求多少种方案能使得它们的和恰好为n,结果自然是(n+RLn)

题目3:现在有n个位置数a1,a2,,an,要求为这n个位置数赋值,并且满足LiaiRi,且序列严格递增,问有多少种不同的赋值方案(两个方案不同当且仅当存在一个未知数被赋予不同的值),其中n200,0LiRi109

我们可以将所有边界排序,之后利用这些边界将取值范围分隔为O(n)个区间。之后我们可以在上面进行DP,记录dp(i,j)表示仅考虑前j个数,且这j个数仅从前i个区间中取值,有多少种方案。可以很容易得出转移关系:dp(i,j)=jk=0dp(i1,k)f(i,jk)[k+1ii],这里f(i,jk)表示从第i个区间中取出长度为jk的严格递增序列的方案数,这个题目1已经解答了。总的时间复杂度为O(n3)

题目4:现在有n个位置数a1,a2,,an,要求为这n个位置数赋值,并且满足LiaiRi,且序列非严格递增,问有多少种不同的赋值方案(两个方案不同当且仅当存在一个未知数被赋予不同的值),其中n200,0LiRi109

同题目3,但是f(i,jk)表示从第i个区间中取出长度为jk的非严格递增序列的方案数,这个已经由题目2解答过了。

题目5:给出一颗n个顶点树,树根为1。问这颗树有多少拓扑排序的方案,将结果模上p=109+7。(如果树中存在边(u,v),且uv的父亲,那么u必须排在v之后)数据满足n106

考虑以某个顶点u为根的排序数为f(u),那么很显然要对其排序等价于对所有子树进行排序,之后将子树合并。比如说u下有孩子abc,那么f(u)=f(a)f(b)f(c)(Su1)!Sa!Sb!Sc!,其中Su表示以u为根的子树拥有多少孩子。

这样我们只需要遍历一次树就可以算出结果了,加上预处理组合所需要的时间,总的时间复杂度为O(n)

题目6:给出一颗n个顶点树,树根为1。现在要求给每个顶点赋值为1m中某个数,要求每个顶点分配到的数不同,且要求每个顶点的数都必须小于自己的父亲。问有多少种方案,结果对p=109+7取模。(两种方案不同当且仅当存在一个顶点,在两个方案中被赋予相同的值)数据满足n106m109

首先利用题目5的方式计算出所有的拓扑排序的方案数T,接下来对于一个拓扑排序得到的序列,我们要为其以递增的方式分配1m中的数,每个数最多出现一次。那么结果就是T(mn),由于m比较大,我们这边利用(mn)=m(mn+1)n!公式求解即可,总的时间复杂度为O(n)

题目7:给出一颗n个顶点树,树根为1。现在要求给每个顶点赋值为1m中某个数,且要求每个数都必须小于等于自己的父亲。问有多少种方案,结果对p=109+7取模。(两种方案不同当且仅当存在一个顶点,在两个方案中被赋予相同的值),数据满足n5000,m109

我们记录函数fu(i)表示以顶点u为根的子树,如果u被赋值为i,整颗子树的方案数。那么当u是叶子顶点的时候,一定有fu(x)=1。同时我们顺便定义Fu(x)=xi=1fu(i),即前缀和函数。

现在考虑任意一个顶点u,假如其有三个子顶点a,b,c,那么一定有fu(i)=Fa(i)Fb(i)Fc(i)

由于1是多项式,而任意多项式的前缀和还是多项式,多项式的乘积依旧是多项式,因此对于任意顶点ufu(x)Fu(x)都是多项式。

注意到当一个多项式从某个顶点沿着向父亲的边来到父亲时,需要修正为前缀和函数,这时候多项式的阶数会增加1。总共有n1条边,而叶子顶点都是0阶多项式,因此我们可以断定f1(x)n1阶多项式,而F1(x)n阶多项式。

我们想要的结果实际上是F1(m)

注意到fu(x)要修正为Fu(x),我们必须要借助多项式插值技术,但是这里每次插值的时间复杂度是O(n2),总共最多会发生O(n)次插值,因此最坏情况下时间复杂度会提高到O(n3)。因此我们需要使用一个技巧,我们并不以系数的形式保存多项式,而是以点值的形式保存多项式。我们需要计算出每个多项式在1,,n+1n+1个点上的值,这与我们就有能力以O(n)的时间复杂度将多项式修正为前缀和形式,以及O(n)的时间复杂度内完成多项式乘法。(注意这里提到的所有多项式在1处的值都应该是1)。

总的时间和空间复杂度是O(n2)

提供一道CF题目

题目8:给出一颗n个顶点树,树根为1。现在要求给每个顶点赋值为1m中某个数,且要求每个数都必须小于自己的父亲。问有多少种方案,结果对p=109+7取模。(两种方案不同当且仅当存在一个顶点,在两个方案中被赋予相同的值),数据满足n3000,m109

题目7的严格递减版本。现在考虑任意一个顶点u,假如其有三个子顶点a,b,c,那么一定有fu(i+1)=Fa(i)Fb(i)Fc(i)

我们为每个多项式维护2n个点的值,分别为1,2,,2n。这里我们定义Du表示u的高度,一个顶点的高度为其到子树内最远的叶子顶点的距离。而对于顶点u,其真正具有意义的点值是Du+1,Du+2,,2n+1

最终的做法也是插值出多项式求解。

问题9:给出一个1,2,,n的排列,判断是否可以将排列拆分为两个递增序列(空序列也认为是递增序列),如果可以,输出任意一种拆分方案,其中n106

我们维护两个递增序列,且由于递增序列只有最后一个元素会对后面的结果产生影响,因此我们可以始终假设第一个递增序列的尾部元素恒小于第二个递增序列的尾部元素。

这里我们可以使用一个贪心策略。当新加入一个元素的时候,若它比第二个递增序列尾部的元素还要大,就放在第二个序列尾部,否则放在第一个序列尾部(如果放不下就无解)。

由于前i个元素中的最大值一定会作为第二个递增序列尾部出现,因此我们要尽可能保证第一个序列尾部的元素尽可能小。

时间复杂度为O(n)

问题10:要求计算1,2,,n的所有排列中,有多少排列可以拆分为两个递增序列(空序列也认为是递增序列),结果模上某个素数p,其中n1000

我们可以利用动态规划解决这个问题。定义dp(i,j)表示两个递增序列的总长度为i的时候,已经使用的数中最大值为j有多少种递增序列。

那么可以推出转移公式:

dp(i,j)=jk=1dp(i1,k)

利用前缀和技术可以O(n2)求解。

问题11:将1,2,,n这些数字加入按顺序加入到一个双端队列中(可以加入到任意一端),之后以任意顺序从双端队列弹出元素形成一个排列。问形成的排列中有多少排列,第k个元素为1。结果对某个素数取模。(kn1000)

首先需要发现所谓的加入双端队列,实际上是维护了两个栈,栈中的元素从栈底到栈顶递增。

之后从两个栈总共弹出k1个元素后,一个栈为空,之后弹出1,最后对于剩下的一个非空栈,其中元素有nk个,对应的形成的序列为2nk1(如果两个栈都空就只有1种序列)。

由于前k1个元素是由两个递减序列所形成的,现在的问题就是求解长度为k1的可以拆分为两个递减序列的序列有多少种,之后乘上2nk1即可。求解的方式在问题10中已经提到过了。

提供一道Atcoder题目

生成函数

生成函数可以用于求解大量统计方案数问题。其方式就是定义一个多项式P(x)=icixi,其中系数就ci表示做i次决策的方案数。现在考虑两个不同方案的生成函数AB,我们现在希望求两个方案总共决策k次的方案数,其实就是求A(x)B(x)xk项的系数。

题目1:给定大小为n的集合A=a1,,an,对于任意A的子集S,记f(S)=aSa,同时记g(i)=SA|S|=if(S)。现在要求输出g(1),,g(n)。其中n105

一般这种问题需要用DP求解,通过各种优化时间复杂度可以达到O(n2),但是还是过不了的。

但是生成函数可以给出更优的做法。我们定义n个多项式P1,,Pn,其中Pi(x)=1+aix。会发现ni=1Pi实际上就等于g(i)。而要计算n个一阶多项式的乘积,可以用分治+FFT的技术实现。时间复杂度为O(n(log2n)2)

题目2:给定两个多重集合AB,其中1|A|,|B|106,且对于AB中的任意元素x都有0x106。现在定义f(n)=aAbB[ban],要求输出所有的f(0),f(1),,f(106)

这个问题可以用bitset来优化,时间复杂度为O(101264)

这和个问题可以用生成函数解决。定义两个多项式PQ,其中Pi表示iA中的出现次数,同理Qi表示iQ中的出现次数。可以发现

f(n)=i=nPiQin

上面的部分是PQ等差卷积后xn项的系数。因此我们只需要将PQ等差卷积后得出所有项的系数。时间复杂度为O(106log2106)

题目3:给定一个序列a1,a2,,an,要求计算有多少i<j<k,满足aiajat=T,其中T是给定常量,且1ai106, 1n1061T106

首先顺序处理起来比较麻烦,但是我们可以求出无序情况下的结果数ans,那么答案就是ans3!

这个问题中要求下标不同,这个条件处理起来太麻烦了,我们用容斥忽略这个条件,具体来说就是记A表示所有满足总和等于T的三元组中满足ij的三元组,B表示满足jk的三元组,C表示满足ki的三元组。那我们要求的是|ABC|的大小。

对于全集,我们可以用生成函数来求。具体做法就是定义一个生成函数P,其中Pi表示i在序列a中的出现次数。那么答案就是P3(x)xT项的系数。

对于|¯A|的统计,即我们已知ik是相同的,我们可以遍历可能的nij的共同下标,定义一个新的多项式QQi表示有多少下标j满足a2j=i。那么答案就是P(x)Q(x)xT项系数。对于|¯B||¯C|,其值与|¯A|相同。

对于其余情况,一定有三个下标都相同,我们可以暴力枚举所有n种下标,来统计。

因此最终总的时间复杂度为O(n+106log2106)

网格问题

题目1:给定一个n×m的矩阵,要求每个网格都要放一个非负数,且第i行所有网格值的和为ai,且要求每一列的网格和为正数。满足该条件的放置数记作f(m),要求分别输出f(1),,f(M)。其中n10,m105,ai1

xi,j表示网格(i,j)所放的数字。记g(m)表示列数为m,且忽略每列的和都必须为正数这个条件下的放置数。可以发现g(m)=ni=1(m+ai1m)。我们可以通过容斥从g中搞出f

f(m)=mi=0(1)i(mi)g(mi)=m!mi=0[(1)i1i!][g(mi)1(mi)!]

可以发现符号内部的内容可以通过对两个多项式计算卷积得到。因此我们可以O(Mlog2M)时间复杂度内求出结果。

提供一道CF题目

题目2:给定一个n×m的矩阵,每个单元中都有一个数,其中单元(i,j)的数为a(i,j),且第一列和第一行的数均为1。且对于1<i,j,满足a(i,j)=a(i1,j)+a(i,j1),要求计算a(n,m),其中n,m105

这个问题实际上要求的是从第(0,j)以及(i,0)(n,m)的路径总数,答案为ni=1(n+m1im1)+mi=2(n+m1in1)。我们可以O(n+m)求解。

统计多重集合

给定包含n种元素的多重集合,第i种元素出现ci次。

有多少种排列?就是普通的多重排列。

(ni=1ci)!ni=1ci!

有多少不同的子集?

ni=1(ci+1)

题目1:给定一个大小为n的背包。记ci为重量为i的物品种类的数量。要求找出能将背包装满的物品的多重集合的数目,结果模上109+7。其中1n10001ci109

很显然这是一个背包问题,但是物品的种类数过多,所以不能用常规的动态规划求。

我们可以记dp(i,j)表示用重量不超过i的物品,有多少不同的多重集合可以填充大小为j的背包。

于是乎可以得到转移:

dp(i,j)=jik=0dp(i1,jki)(ci+k1k1)

由于(ci+k1k1)k1非常小,我们可以O(k)求解,但是特殊的可以发现k是递增的且(ci+k1k1)=(ci+k2k2)ci+k1k1,因此我们可以O(1)递推求出每个组合数。

因此上面的递推公式可以O(ji)求出,要求所有的状态总的时间复杂度为O(ninjji)=O(n2logn)

提供一道问题

Hockey Stick Identity

ni=0(ir)=(n+1r+1)

可以归纳证明,当nr的时候显然成立。考虑n>r的情况下,有

ni=0(ir)=(nr+1)+(nr)=(n+1r+1)

提供一些题目

参考资料