组合数求法、卡特兰数
一.组合数的计算方法
首先组合和排列数的定义在高中阶段已经知晓,这里主要探讨在算法竞赛中的应用。首先,我们通常把 C n m C_{n}^{m} Cnm写成 ( n m ) \tbinom{n}{m} (mn)的形式(注意上下顺序是反过来的),初次看这种格式可能会不太适应。下面用一个例题配合多种数据范围介绍几种常用的组合数求法。
例题:给定 n 组询问,每组询问给定两个整数 a,b,请你输出 ( a b ) % P \tbinom{a}{b} \%P (ba)%P的值。
方法1
将1,2两种方案相加就是在a个苹果中选择b个苹果。
code也很简洁
//假设数据范围a<=2000,模数P固定void init()//预处理所有组合数出来
{for(int i=0;i<=2000;i++){for(int j=0;j<=i;j++){if( !j ) c[i][j] = 1;else c[i][j] = (c[i-1][j] + c[i-1][j-1] )%P;}}
} int main()
{cin>>n;init();while( n-- ){scanf("%lld %lld",&a,&b);printf("%lld\n",c[a][b]);}return 0;
}
方法2
当P是质数时, i n v ( x ) = x P − 2 inv(x) = x^{P-2} inv(x)=xP−2,用快速幂即可
code:
const LL p = 1e9+7;//假设模数固定
LL quick_pow(LL a,LL b,LL p)//快速幂函数
{LL ans = 1;while( b ){if( b&1 ) ans = ans * a % p;a = a*a % p;b>>=1;}return ans;
}LL n,fact[N],infact[N],a,b;//fact存阶乘,infact存阶乘的逆元void init()
{fact[0] = infact[0] = 1;for(int i=1;i<N;i++){fact[i] = ( fact[i-1] * i ) % p;infact[i] = ( infact[i-1] * quick(i,p-2,p) ) % p;}
}int main()
{scanf("%lld %lld",&a,&b);printf("%lld\n", fact[a] * infact[b] % MOD * infact[a-b] % MOD );return 0;
}
方法3
//卢卡斯定理#define LL long long
const LL p = 1e5+7;
LL quick(LL a,LL b,LL p)//依然是快速幂求逆元
{LL res = 1;while( b ){if( b&1 ) res = res * a % p;a = a * a % p;b>>=1;}return res;
}LL C(LL a,LL b,LL p)//把数值拆的很小后,用组合数定义公式求数
{if( b>a ) return 0;LL ans = 1;for(int i=1,j=a;i<=b;i++,j--){ans = ans * j % p;ans = ans * quick( i,p-2,p ) % p;}return ans;
}LL lucas(LL a,LL b,LL p)//卢卡斯定理
{if( a<p && b<p ) return C(a,b,p);return C( a%p,b%p,p ) * lucas( a/p,b/p,p )%p;
}int main()
{LL n,a,b;scanf("%lld %lld",&a,&b);printf("%lld\n",lucas(a,b,p));return 0;
}
二.卡特兰数
有很多种方式可以推出卡特兰数的公式,这里选择较为通俗易懂的方式来讲述。(毕竟别的比如生成函数来推导太噩梦了 )我们从几个经典的问题来了解一下卡特兰数。
经典问题1:进出栈序列
题目描述
n 个元素进栈序列为:1,2,3,4,…,n,则有多少种出栈序列。
(这是一道最经典的入门级卡特兰数题目,后面的例题跟这个差不多。)
分析:我们将进栈表示为 +1,出栈表示为 -1,则 1 3 2 的出栈序列可以表示为:+1 -1 +1 +1 -1 -1。过程如下图。
根据栈本身的特点,每次出栈的时候,必定之前有元素入栈,即对于每个 -1 前面都有一个 +1 相对应。因此,出栈序列的所有前缀和必然大于等于 0,并且 +1 的数量 等于 -1 的数量。
接下来让我们观察一下 n = 3 的一种出栈序列:+1 -1 -1 +1 -1 +1。序列前三项和小于 0,显然这是个非法的序列。
如果将 第一个 前缀和小于 0 的前缀,即前三项元素都进行取反,就会得到:-1 +1 +1 +1 -1 +1。此时有 3 + 1 个 +1 以及 3 - 1 个 -1。
因为这个小于 0 的前缀和必然是 -1,且 -1 比 +1 多一个,取反后,-1 比 +1 少一个,则 +1 变为 n + 1 个,且 -1 变为 n - 1 个。进一步推广,对于 n 元素的每种非法出栈序列,都会对应一个含有 n + 1 个 +1 以及 n - 1个 -1 的序列。
那么我们会有一个问题:如何证明这两种序列是一一对应的?
可以假设非法序列为 A,对应的序列为 B。每个 A 只有一个"第一个前缀和小于 0 的前缀",所以每个 A 只能产生一个 B。而每个 B 想要还原到 A,就需要找到"第一个前缀和大于 0 的前缀",显然 B 也只能产生一个 A。
每个 B 都有 n + 1 个 +1 以及 n - 1 个 -1,因此 B 的数量为 ( 2 n n + 1 ) \tbinom{2n}{n+1} (n+12n),相当于在长度为 2n 的序列中找到n + 1个位置存放 +1。相应的,非法序列的数量也就等于 ( 2 n n + 1 ) \tbinom{2n}{n+1} (n+12n)。出栈序列的总数量共有 ( 2 n n ) \tbinom{2n}{n} (n2n),因此,合法的出栈序列的数量为 ( 2 n n ) − ( 2 n n + 1 ) \tbinom{2n}{n} - \tbinom{2n}{n+1} (n2n)−(n+12n)。
到此为止,我们已经得到了卡特兰数的通项 ( 2 n n ) − ( 2 n n + 1 ) = ( 2 n n ) n + 1 \tbinom{2n}{n} - \tbinom{2n}{n+1} = \frac{ \tbinom{2n}{n} }{n+1} (n2n)−(n+12n)=n+1(n2n) 。
经典问题2:括号序列
题目描述
n 对括号,则有多少种 “括号匹配” 的括号序列
分析: 左括号看成 +1,右括号看成 -1,那么就和上题的进出栈一样,共有 ( 2 n n ) − ( 2 n n + 1 ) = ( 2 n n ) n + 1 \tbinom{2n}{n} - \tbinom{2n}{n+1} = \frac{ \tbinom{2n}{n} }{n+1} (n2n)−(n+12n)=n+1(n2n) 种序列。
经典问题3:买票找零问题
题目描述
电影票一张 50元,且售票厅一开始没有零钱。m 个人各自持有 50元,n 个人各自持有 100元。则有多少种排队方式,可以让每个人都买到电影票。
分析:持有 50 元的人每次购票时不需要找零,并且可以帮助后面持有 100元的人找零;而对于持有 100元的人每次购票时需要找零,但100元对后面的找零没有任何作用。
因此,相当于每个持有 100元 的人都需要和一个持有 50元 的人进行匹配。我们将持有 50元的标记为 +1,持有 100元的标记为 -1,此时我们会惊喜地发现这个问题跟经典问题1也是差不多的!
但是但是,不同的是,这题 m 并一定等于 n,且排队序列是一种排列,需要考虑先后顺序,例如各自持有 50元 的两个人的前后关系互换会造成两种不同的排队序列。故在此我们乘上相应排列的数目,最后答案是 [ ( n + m m ) − ( n + m m + 1 ) ] × n ! × m ! [\tbinom{n+m}{m} - \tbinom{n+m}{m+1}] \times n! \times m! [(mn+m)−(m+1n+m)]×n!×m!种。
代码实现
上面我们已经推出了通式
C n = ( 2 n n ) − ( 2 n n + 1 ) = ( 2 n n ) n + 1 C_n = \tbinom{2n}{n} - \tbinom{2n}{n+1} = \frac{ \tbinom{2n}{n} }{n+1} Cn=(n2n)−(n+12n)=n+1(n2n)
根据公式配合组合数计算方法这可以直接求值了。除了这个通项公式之外,卡特兰数还有一个由该通项公式推导而来的递推公式更加常用:
C n + 1 = 4 n + 2 n + 2 C n C_{n+1} = \frac{4n+2}{n+2} C_n Cn+1=n+24n+2Cn
递推边界是 C 0 = 1 C_0=1 C0=1
一个例题
int n;
long long f[25];//存储卡特兰数void solve()
{f[0] = 1;cin >> n;for (int i = 1; i <= n; i++) f[i] = f[i - 1] * (4 * i - 2) / (i + 1);// 这里用的递推cout << f[n] << endl;return 0;
}
除了本文介绍的几种经典问题,还有很多问题最后也是可以推导到卡特兰数上的。感兴趣可以自行找资料。
组合数求法、卡特兰数相关推荐
- 【算法专题】卡特兰数
卡特兰数 1. 概述 卡特兰数:首先这个一个数,很多问题的结果都是卡特兰数,比如2016年全国三卷数学选择题压轴题让求解的就是卡特兰数,问题如下: 首先是结论:卡特兰数为: C 2 n n n + 1 ...
- Catalan数——卡特兰数
今天阿里淘宝笔试中碰到两道组合数学题,感觉非常亲切,但是笔试中失踪推导不出来 后来查了下,原来是Catalan数.悲剧啊,现在整理一下 Catalan数--卡特兰数] 一.Catalan数的定义令h( ...
- 洛谷 P1044 栈 [卡特兰数]
题目背景 栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表. 栈有两种最重要的操作,即 poppop (从栈顶弹出一个元素)和 pushpush (将一个元素进栈). 栈 ...
- 牛客 - 弦(卡特兰数)
题目链接:点击查看 题目大意:给定一个圆,圆上有2N个互不重叠的点.每次操作随机选择两个先前未选择过的点连一条弦,共连成N条弦,求所有弦不交的概率. 题目分析:圆内连弦是卡特兰数的经典应用,如果将最后 ...
- HDU 5673 Robot 卡特兰数
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5673 题目描述: 一个人从原点开始向右走, 要求N秒后回到原点, 且过程中不能到负半轴, 人有两种操 ...
- 知识点 组合数学 卡特兰数
关于卡特兰数 卡特兰数是一种经典的组合数,经常出现在各种计算中,其前几项为 : 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, ...
- BZOJ4001 TJOI2015概率论(生成函数+卡特兰数)
设f(n)为n个节点的二叉树个数,g(n)为n个节点的二叉树的叶子数量之和.则答案为g(n)/f(n). 显然f(n)为卡特兰数.有递推式f(n)=Σf(i)f(n-i-1) (i=0~n-1). 类 ...
- 信奥中的数学:斯特林数、卡特兰数
P1287 盒子与球(球不同 盒不同 不允许有空盒) 盒子与球 - 洛谷 第二类斯特林数总结 第二类斯特林数总结 - _zjz 的博客 - 洛谷博客 P4091 [HEOI2016/TJOI2016] ...
- 2017百度之星程序设计大赛 - 资格赛【1001 Floyd求最小环 1002 歪解(并查集),1003 完全背包 1004 01背包 1005 打表找规律+卡特兰数】...
度度熊保护村庄 Accepts: 13 Submissions: 488 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/3276 ...
最新文章
- 面试官:如何停止一个正在运行的线程?我一脸蒙蔽...
- 2021年春季学期-信号与系统-第二次作业参考答案-第四小题
- c语言倒置存放,c语言倒置
- 使用curl登陆上网账号
- Ubuntu输入密码登陆后又跳回到登录界面
- 基于COM的矢量图像控件VectorDraw
- Django项目部署(nginx1.18+uwgsi)
- lisp语言如何画小红点_实验四、五 用AutoCADLISP语言编程绘图
- 查询天地图访问配额 https://console.tianditu.gov.cn/api/statistics
- 叠螺机_叠螺机如何实现全自动喷淋维护
- 电路与电子技术课程设计报告(正弦、方波-三角波、可调矩形波、可调锯齿波发生器)
- mysql 80070057_0x80070057错误原因
- 社区价值:福山论自组织、社区、社会资本
- oracle RAC 集群无法启动
- 2011年新的个人纳税情况
- Android画图方式
- 北京办理居住证的全流程
- python爬虫-爬取股票贴吧帖子
- Windows下Python的安装
- mysql查询姓张的同学_Mysql 基础2 (sql查询语句)
热门文章
- 自学成才翁_如何获得自学成才的第一份工作
- 使用TPOT自动选择scikit-learn机器学习模型和参数
- MODBUS协议详解
- 利用Python将图片序列转换成视频
- 非静压模型SWASH学习(2)——潮汐波模拟算例(Tidal wave flow over an irregular bed)
- 晶圆测试开发软件,晶圆级可靠性测试:器件开发的关键步骤(一)
- 代码分析栈(stack)的生长方向
- macos使用nullximpactor为ios设备安装未签名app和越狱版app
- 抽样分布概念及其三大重要分布
- 指针--用指针变量作函数参数的实例(按值调用与模拟按引用调用)、函数指针及其应用