本文讲解卡特兰数的各种递推公式,以及卡特兰数、卡特兰大数、卡特兰大数取模的代码实现,最后再顺带提一下卡特兰数的几个应用。
什么是卡特兰数呢?卡特兰数无非是一组有着某种规律的序列。重要的是它的应用。卡特兰数前几项为 : 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452, ...
下面给出几个求卡特兰数的公式,用h(n)表示卡特兰数的第n项,其中h(0)=1,h(1)=1
公式一:h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (n>=2)
公式二:h(n)=h(n-1)*(4*n-2)/(n+1)
公式三:h(n)=C(2n,n)/(n+1) (n=0,1,2,...)
公式四:h(n)=c(2n,n)-c(2n,n-1)(n=0,1,2,...)
下面代码用到的是公式一、公式二和公式四。
根据公式一求n<=35以内的卡特兰数,由于卡特兰数很大,超过35就超了long long 型了,所以n<=35时可以用公式一求解:
void init()
{
int i,j;
LL h[36];
h[0]=h[1]=1;
for(i=2;i<36;i++)
{
h[i]=0;
for(j=0;j
如果n>35时求h(n)%p应该怎么求呢?由于h(n)是大数,这里可以借助Lucas(卢卡斯)定理来解决。
Lucas定理:Lucas定理是用来求 c(n,m) mod p,p为素数的值。Lucas定理的表达式为:Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p) 有了这个公式,我们直接看Lucas定理的代码:
//Lucas定理实现C(n,m)%p的代码:
LL exp_mod(LL a, LL b, LL p)
{ //快速幂取模
LL res = 1;
while(b != 0)
{
if(b&1) res = (res * a) % p;
a = (a*a) % p;
b >>= 1;
}
return res;
}
LL Comb(LL a, LL b, LL p)
{ //求组合数C(a,b)%p
if(a < b) return 0;
if(a == b) return 1;
if(b > a - b) b = a - b;
LL ans = 1, ca = 1, cb = 1;
for(LL i = 0; i < b; ++i)
{
ca = (ca * (a - i))%p;
cb = (cb * (b - i))%p;
}
ans = (ca*exp_mod(cb, p - 2, p)) % p;
return ans;
}
LL Lucas(LL n,LL m,LL p)
{ //Lucas定理求C(n,m)%p
LL ans = 1;
while(n&&m&&ans)
{
ans = (ans*Comb(n%p, m%p, p)) % p;
n /= p;
m /= p;
}
return ans;
}
这样根据公式四:h(n)=c(2n,n)-c(2n,n-1)(n=0,1,2,...) 就可以利用Lucas定理来求 :
h(n)%p=(Lucas(2n,n,p)-Lucas(2n,n-1,p)+p)%p。怎么理解呢?对于两个数a,b,(a-b)%p=(a%p-b%p)%p;那括号里为什么还要再加一个p呢?因为取模前前者一定大于后者,相减一定为正,而取模后就不一定了,所以要加一个p,保证是正数。
如果是要求卡特兰大数呢?只要顺便实现大数的一些运算就好了。下面直接给出代码:
int a[101][101],b[101];
void catalan() //求卡特兰数
{
int i, j, len, carry, temp;
a[1][0] = b[1] = 1;
len = 1;
for(i = 2; i <= 100; i++)
{
for(j = 0; j < len; j++) //乘法
a[i][j] = a[i-1][j]*(4*(i-1)+2);
carry = 0;
for(j = 0; j < len; j++) //处理相乘结果
{
temp = a[i][j] + carry;
a[i][j] = temp % 10;
carry = temp / 10;
}
while(carry) //进位处理
{
a[i][len++] = carry % 10;
carry /= 10;
}
carry = 0;
for(j = len-1; j >= 0; j--) //除法
{
temp = carry*10 + a[i][j];
a[i][j] = temp/(i+1);
carry = temp%(i+1);
}
while(!a[i][len-1]) //高位零处理
len --;
b[i] = len;
}
}
以上可以处理n<=100时的卡特兰大数,n再大可以把数组相应开大。其中b[i]保存的是第i位卡特兰数的长度,a[i]数组保存的是第i位卡特兰数的数值,高位存高位,低位存低位。
最后再简单说一下卡特兰数的应用,网上也给出了很多很好的解析,我只是把我觉得重要的整合了一下:
卡特兰数的应用都可以归结到一种情况:有两种操作,分别为操作一和操作二,它们的操作次数相同,都为 N,且在进行第 K 次操作二前必须先进行至少 K 次操作一,问有多少中情况?结果就Catalan(N)。
Catalan数的典型应用:
1.由n个+1和n个-1组成的排列中,满足前缀和>=0的排列有Catalan(N)种。
2.括号化问题。左括号和右括号各有n个时,合法的括号表达式的个数有Catalan(N)种。
3.有n+1个数连乘,乘法顺序有Catalan(N)种,相当于在式子上加括号。
4.n个数按照特定顺序入栈,出栈顺序随意,可以形成的排列的种类有Catalan(N)种。
5.给定N个节点,能构成Catalan(N)种种形状不同的二叉树。
6.n个非叶节点的满二叉树的形态数为Catalan(N)。
7.对于一个n*n的正方形网格,每次只能向右或者向上移动一格,那么从左下角到右上角的不同种类有Catalan(N)种。
8.对于在n位的2进制中,有m个0,其余为1的catalan数为:C(n,m)-C(n,m-1)。
9.对凸n+2边形进行不同的三角形分割(只连接顶点对形成n个三角形)数为Catalan(N)。
10.将有2n个元素的集合中的元素两两分为n个子集,若任意两个子集都不交叉,那么我们称此划分为一个不交叉划分。此时不交叉的划分数是Catalan(N)。
11.n层的阶梯切割为n个矩形的切法数也是Catalan(N)。
12.在一个2*n的格子中填入1到2n这些数值使得每个格子内的数值都比其右边和上边的所有数值都小的情况数也是Catalan(N)。