组合计数初步

前言

组合数学主要是研究某组离散对象满足一定条件的安排的存在性、构造及计数等问题。组合计数理论是组合数学中一个最基本的研究方向,主要研究满足一定条件的安排方式的数目及其计数问题。本课程主要介绍组合数学中常见的和重要的一些计数原理、计数方法和计数公式,包括一般的排列、组合的计算以及生成函数、容斥原理、反演原理、$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这个不好理解啊...要好好想想。


 

posted @ 2019-10-11 21:39  haruka酱  阅读(1272)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end