组合计数初步
前言
组合数学主要是研究某组离散对象满足一定条件的安排的存在性、构造及计数等问题。组合计数理论是组合数学中一个最基本的研究方向,主要研究满足一定条件的安排方式的数目及其计数问题。本课程主要介绍组合数学中常见的和重要的一些计数原理、计数方法和计数公式,包括一般的排列、组合的计算以及生成函数、容斥原理、反演原理、$Polya$计数定理等等,是研究组合数学的初步。
(以上来自Baidu)
由于组合计数是$CSPS2019$的重点押题对象,所以写一下。
组合的表示:$C(n,m)$表示从$n$个数当中选$m$个出来的方案数。
排列的表示:$A(n,m)$表示从$n$个数中找出$m$种排列。
排列组合的基本性质
1.$C(n,m)=C(n-1,m)+C(n-1,m-1)$
2.$C(n,m)=n!/(m!*(n-m)!)$
3.$A(n,m)=n!/(n-m)!$
4.$\sum_{i=0}^{n} C_n^i = 2^n $
5.二项式定理:$(x+y)^n=\sum_{k=0}^{n} C(n,k)x^{k}y^{n-k}$
看到这里大家一定对排列组合有了一些基本了解了,辣么我们来做一些可爱的小练习吧!!
排列组合入门经典例题
例1 POJ1942 Paths On A Grid
有一张$n*m$的网格纸,求从左下到右上的方案数,每次只能向上或向右走。
分析:如果我们要从左下走到右上,那么一共就会走$n+m$步,而这$n+m$步当中我们有$n$步是在往上走的,由于每一步向右或者向上走都会计算成不同的方案,所以$n+m$中可以选不同的位置来走这$n$步,所以最后的方案就是$C(n+m,n)$,可能有同学会问(其实是我自己),为啥不是$n+m$选$m$个呢?其实你把它代入公式后就会发现,结果是一样的2333..答案绝对不是$C(n+m,n)+C(n+m,m)$,因为你在选择一种$n$步的时候那么另外$m$步就确定了,相加就重复了。
代码:
#include<cstdio> #define min(x,y) ((x)<(y))?(x):(y) unsigned long long n,m; int main() { while(~scanf("%llu%llu",&n,&m)) { if(!n&&!m)break; unsigned long long sum=n+m; n=min(n,m); unsigned long long ans=1; for(int i=1;i<=n;i++) ans*=(sum-i+1),ans/=i; printf("%llu\n",ans); } return 0; }
本来想补一道题:$AGC001E$ 但是我自己也不想写了,所以大家去搜搜这道题,自行脑补吧。
例2 Catalan数
$Catalan$数(卡特兰数)是经典的一类组合数,公式可简化为$ h(n)=C(2n,n)/(n+1) $或$f(n)=f(n-1)*(4n-2)/(n+1)$。有几类运用。
进出栈问题
有一个进栈顺序为$1,2,3...n$的序列,求所有可能的出栈顺序。
若最后一个出栈的数为$k$,设比$k$早进栈早出栈的数有$k-1$个,那么比k晚进栈早出栈的数就有$n-k$个,那么这种情况下就有$h(k-1)*h(n-k)$个可能的出栈顺序。由于k的取值不同而出现的所有情况相对独立,最终的结果就是$h(n)=h(n-1)*h(0)+h(n-2)*h(1)+....+h(0)*(n-1)$。化简下来得到原公式。
证明?我不会。
n个节点的二叉树&n凸边形
一个有$n$个节点的二叉树的构造方式是卡特兰数,一个$n$凸边形,连接两两顶点划分成不同三角形的方案的公式也是卡特兰数。
不越过对角线的方案数
有一个n*n的网格,要求我们从$(0,0)$走到$(n,n)$,求不越过对角线的方案数。
我们首先考虑不加任何限制的总方案数,其实就是例1中的公式,$C_{2n}^n$,直接求不越过对角线的方案数好像很麻烦,这里我们考虑容斥原理一波,总方案数-越过对角线的方案数=不越过对角线的方案数。
显而易见,所有越过对角线的方案必经过$y=x+1$这条线上的一点。
那么我们把下图中蓝色的部分翻转过来,越过对角线的方案就能转化为从$(-1,1)$走到$(n,n)$的方案数。
显而易见,我们仍然可以套公式,此时的公式转化为$C(2n,n-1)$或者$C(2n,n+1)$。
显而易见是加法原理,所以可以相减化简:
$C(2n,n)-C(2n,n-1)$ = $\frac{(2n)!}{n!n!}$-$\frac{(2n)!}{(n+1)!(n-1)!}$
= $\frac{(2n)!(n+1)}{n!(n+1)!}$-$\frac{(2n)!n}{n!(n+1)!}$
=$\frac{(2n)!}{(n+1)!n!}$
=$\frac{(2n)!}{n!n!(n+1)}$
=$\frac{C(2n,n)}{n+1}$
这是啥啊?这是卡特兰数。
括号匹配问题
有$n$个左括号和$n$个右括号,求这$2n$个括号相匹配后合法的总方案数。
首先我们仍然来考虑总方案数,问题转化成有$2n$个括号,我们要从中选出$n$个括号来作为左括号/右括号放在我们长为$2n$的括号序列中,那么此时的方案数为$C(2n,n)$,考虑完总方案数,根据容斥原理来讲,我们要求出不合法的总方案数,也就是说至少有一个括号不合法的方案数,这就相当于我们选择$n-1$个括号作为右括号/左括号,$n+1$个括号作为左括号/右括号,方案数为$C(2n,n-1)$/$C(2n,n+1)$,那么合法的方案数最后求出来仍然是卡特兰数,这种运用容斥原理转换问题的方式,我们称为补集转换。
n个1和n个-1构成的序列前缀和始终大于等于0
证明和前面差不多。
$excatalan$
给定如上的$2n$个括号,求满足一共有m对括号不匹配的序列的总数。
其实只是一个简单的变形。
我们已经知道,如果至少有一对不匹配的总方案数是$C(2n,n+1)$,所以我们完全可以推广到至少有$m$对不匹配的总方案数是$C(2n,n+m)$,那么如果恰好有$m$对不匹配,就用至少有$m$对括号不匹配的方案数减去至少有$m+1$对括号不匹配的方案数,就求得了答案。
代码:
#include<cstdio> #define int long long const long long mod=998244353; using namespace std; int n,m,fac[20000005],inv[20000005]; int pow(int a,int b) { int ans=1; while(b) { if(b&1)ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } void pre() { fac[0]=inv[0]=1; for(int i=1;i<=2*n;++i) { fac[i]=(fac[i-1]*i)%mod; inv[i]=pow(fac[i],mod-2); if(inv[1] != 1) break; } } int C(int n,int m){return fac[n]*inv[m]%mod*inv[n-m]%mod;} signed main() { freopen("excatalan.in","r",stdin); freopen("excatalan.out","w",stdout); scanf("%lld%lld",&n,&m); pre(); printf("%lld\n",(C(2*n,n+m)-C(2*n,n+m+1) + mod) % mod); return 0; }
例3 [HNOI2008]越狱
监狱有连续编号为1...N1...N的N个房间,每个房间关押一个犯人,有M种宗教,每个犯人可能信仰其中一种。如果相邻房间的犯人的宗教相同,就可能发生越狱,求有多少种状态可能发生越狱。
分析:乍一看这道题如果顺推并不好求,我们是否考虑一下问题转换呢?运用在Catalan数中讲到的补集转换,我们考虑求出总方案数,即$M^N$个,再考虑不会发生越狱的情况:
如果第一个犯人有$M$种宗教来选择,那么第二个犯人就只能选择其他$M-1$种宗教,第三个犯人就能选择其他$M-2$种宗教加上第一个犯人的一种宗教,就仍然是$M-1$种,第四个犯人就能选择其他$M-3$种宗教加上第一个犯人的一种宗教和第二个犯人的一种宗教。以此类推。
所以总的不合法方案数(不会发生越狱)就是$M*(M-1)^{N-1}$,最终的答案就是$M^N-M*(M-1)^{N-1}$。
#include<cstdio> #define int long long const int mod=1e5+3; int pow(int a,int b) { int ans=1; while(b) { if(b&1)ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } int m,n; signed main() { scanf("%lld%lld",&m,&n); printf("%lld\n",(pow(m,n)-(m%mod*pow(m-1,n-1))%mod+mod)%mod); return 0; }
例4 [THUPC2018]蛋糕
例5 [JSOI2011]分特产
容斥原理
(咕咕 等我想起来再写吧)
Twelve Fold Way
这12个问题是排列组合中很常考,也是非常重要的一类,从前我很杂乱地学习了其中的一些,国庆在CW听课的时候wqy学长系统给我们讲了一下,这里我就按照他的ppt讲解顺序来写。
题目总体描述:
有$n$个有标号/无标号的球放在$m$个有标号/无标号的盒子里,分别满足以下三种情况:
A.每个盒子里放的球数量无限制
B.每个盒子至少放一个球
C.每个盒子至多放一个球
为了方便,我们用L表示有标号,U表示无标号,用三个字母表示文体种类,第一个表示球是否有编号,第二个表示盒子是否有标号,第三个表示情况种类。
LLA
这个情况就相当于,每一种球都有$m$个盒子可以选择,那么$n$个球就应该有$m^n$种情况。
ULC
每个盒子至多放一个,这就说明每个盒子要么放要么不放,而我们是$n$个无标号的球放在$m$个有标号的盒子中,也就是说盒子的选择是有影响的,但球可以随便选,就相当于每次在$m$个盒子中选$n$个出来,那么方案数就是$C(m,n)$。
LLC
这个就相当于,球和盒子的选择都是影响的,并且每个盒子要么放要么不放,那么盒子的选择是$C(m,n)$,当我们确定了盒子的选择就可以把这$n$个盒子排列,也就相当于对$n$个球进行内部的排列,也就是$n!$,在$C(m,n)$的基础上乘以一个$n!$,最终答案就是$A(m,n)$。
ULC
这...
emmm...
好吧还是讲一下...
既然这里有$m$个无标号的盒子,每个盒子要么放一个要么不放,那么你这$n$个球无论放哪都是一样的呀...而如果$n$大于了$m$,就有一些球放不下,所以没有方案。
所以,当$n<=m$,有一种方案;当$n>m$,有$0$种方案。
UUC
我不理解我把它写出来的意义是什么,也许是保证知识的完整性吧。
盒子无标号,球也没有标号,每个盒子要么放一个要么不放,那这个不就是和LUC一样的吗...
所以,当$n<=m$,有一种方案;$当n>m$,有$0$种方案。
ULB
emm...看起来有点难的亚子。
转化一下就是不能有空盒。
首先如果$n<m$,那肯定满足不了条件,方案数就是$0$。
如果不能有空盒,那么在$m$上面做文章似乎有点麻烦,那么不如考虑$n$?
这$n$个球如果排成一行,那么就可以看做它有$n-1$个间隔,需要把它们分成$m$个区间,我们现在考虑在这些间隔中间“插板”,要插$m-1$个板才能够分成$m$个区间,由于盒子有标号,那么每个盒子里只要有一个放的球数量和上一个方案不同,就是一个新的方案,就相当于在$n-1$个间隔中每次选择$m-1$个间隔,满足这$m-1$个间隔不完全相同,那么最终的方案数就应该是$C(n-1,m-1)$。
这就是著名的插板法。(其实我也不知道著不著名,这样听起来比较正经)
ULA
其实我感觉这个不大好理解...$n$个无标号的球放在$m$个有标号的盒子里,允许有空盒。就相当于这m个盒子中,每一个放$xi$个球,$x1+x2+...+xm=n$,$xi>=0$,咋一看似乎可以高斯消元一波?傻子才用辣么复杂的东西...我们考虑ULB的情况,ULB应该是$x1+x2+...+xn=n$,$xi>=1$,也就是说,我们要是把此处的$xi+1$,就能转化为ULB的情况,设$yi=xi+1$,所以$y1+y2+...+ym=n+m,yi>=1$,最终的方案数就是$C(n+m-1,m-1)$。
qwq这个不好理解啊...要好好想想。