卡特兰数(catalan)总结
卡特兰数的公式
递推公式1:$f(n)=\sum \limits_{i=0}^{n-1}f(i)*f(n-i-1)$
递推公式2:$f(n)=\frac{f(n-1)*(4*n-2)}{n+1}$
组合公式1:$f(n)=\frac{C_{2n}^{n}}{n+1}$
组合公式2:$f(n)=C_{2n}^{n}-C_{2n}^{n-1}$
关于卡特兰数的题目
1. 有限制的网格方案数 eg网格
利用组合数的思想:
对于长N宽M的网格(下图2),方案数为 $C_{n+m}^{m}-C_{n+m}^{m-1}$
理解:走到(n,m)这个点总共要走n+m步,其中有m步一定是向上的,所以$C_{n+m}^{m}$这是所有情况
但有不合法的情况,且不合法的一定经过绿线,将原图形沿其翻折,相当于走到c点,此时总n+m步不变,但只有m-1步是向右的
所以$C_{n+m}^{m-1}$是不合法的
(借用kaola学长的图)
对于N×N的网格就是卡特兰数了,如图一
本题先将式子化简,然后将其分解质因数,消去除法,最后乘上每个质数的个数次方就好
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 using namespace std; 6 int n,m,num,p[10005],v[10005]; 7 int sum[10005]; 8 void prime(int x) 9 { 10 for(int i=2;i<=x;i++) 11 { 12 if(!v[i]) {v[i]=i;p[++num]=i;} 13 for(int j=1;j<=num;j++){ 14 if(p[j]>v[i]||i*p[j]>x) continue; 15 v[i*p[j]]=p[j]; 16 } 17 } 18 } 19 int len=1,ans[100000001]; 20 void mul(int x) 21 { 22 int k=0; 23 for(int i=1;i<=len;i++) 24 { 25 ans[i]=ans[i]*x+k; 26 k=ans[i]/10; 27 ans[i]%=10; 28 if(k>0&&i==len) len++; 29 } 30 } 31 int main() 32 { 33 ans[1]=1; 34 scanf("%d%d",&n,&m); 35 prime(n+m+1); 36 int t=n+1-m; 37 while(t>1) 38 { 39 sum[v[t]]++; 40 t/=v[t]; 41 } 42 for(int i=n+m;i>=n+2;i--) 43 { 44 t=i; 45 while(t>1) 46 { 47 sum[v[t]]++; 48 t/=v[t]; 49 } 50 } 51 for(int i=2;i<=m;i++) 52 { 53 t=i; 54 while(t>1) 55 { 56 sum[v[t]]--; 57 t/=v[t]; 58 } 59 } 60 for(int i=1;i<=num;i++) 61 for(int j=1;j<=sum[p[i]];j++) 62 mul(p[i]); 63 for(int i=len;i>=1;i--) 64 printf("%d",ans[i]); 65 puts(""); 66 }
2.有趣的数列
其实这个也可以理解为上一个网格,将偶数位记为向右走一步,奇数位记为向上走一步,,偶数位之和大于奇数位之和,就是不能越过绿线
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #define ll long long 6 using namespace std; 7 const int maxn=2000005; 8 int n,mod,num; 9 ll p[maxn];int v[maxn]; 10 ll sum[maxn]; 11 void prime(int x) 12 { 13 for(int i=2;i<=x;i++) 14 { 15 if(!v[i]) {v[i]=i;p[++num]=i;} 16 for(int j=1;j<=num;j++){ 17 if(p[j]>v[i]||i*p[j]>x) break; 18 v[i*p[j]]=p[j]; 19 } 20 } 21 } 22 ll qpow(int a,int b) 23 { 24 ll ans=1; 25 while(b) 26 { 27 if(b&1) ans=ans*a%mod; 28 a=a*a%mod; 29 b>>=1; 30 } 31 return ans%mod; 32 } 33 int main() 34 { 35 scanf("%d%d",&n,&mod); 36 prime(2*n+1); 37 for(int i=2*n;i>=n+2;i--) 38 { 39 int t=i; 40 while(t>1) 41 { 42 sum[v[t]]++; 43 t/=v[t]; 44 } 45 } 46 for(int i=1;i<=n;i++) 47 { 48 int t=i; 49 while(t>1) 50 { 51 sum[v[t]]--; 52 t/=v[t]; 53 } 54 } 55 ll ans=1; 56 for(int i=1;i<=num;i++) 57 if(sum[p[i]]) 58 ans=ans*qpow(p[i],sum[p[i]])%mod; 59 printf("%lld\n",ans); 60 }
3.树屋阶梯
我们不妨手模样例,若扣去左下角直角所在矩形,
图一和图四的方案数为右面的2块的方案数×上面的0块的方案数,即为$f(3)+=f(2)*f(0)$
同理图二和图五为$f(3)+=f(0)*f(2)$ 图三为$f(3)+=f(1)*f(1)$
由此可得 $f(n)=\sum \limits_{i=0}^{n-1}f(i)*f(n-i-1)$ 卡特兰数公式1
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 using namespace std; 6 int n,num,p[10005],v[10005]; 7 int sum[10005]; 8 void prime(int x) 9 { 10 for(int i=2;i<=x;i++) 11 { 12 if(!v[i]) {v[i]=i;p[++num]=i;} 13 for(int j=1;j<=num;j++){ 14 if(p[j]>v[i]||i*p[j]>x) break; 15 v[i*p[j]]=p[j]; 16 } 17 } 18 } 19 int len=1,ans[10000001]; 20 void mul(int x) 21 { 22 int k=0; 23 for(int i=1;i<=len;i++) 24 { 25 ans[i]=ans[i]*x+k; 26 k=ans[i]/10; 27 ans[i]%=10; 28 if(k>0&&i==len) len++; 29 } 30 } 31 int main() 32 { 33 ans[1]=1; 34 scanf("%d",&n); 35 prime(2*n+1); 36 for(int i=2*n;i>=n+2;i--) 37 { 38 int t=i; 39 while(t>1) 40 { 41 sum[v[t]]++; 42 t/=v[t]; 43 } 44 } 45 for(int i=1;i<=n;i++) 46 { 47 int t=i; 48 while(t>1) 49 { 50 sum[v[t]]--; 51 t/=v[t]; 52 } 53 } 54 for(int i=1;i<=num;i++) 55 for(int j=1;j<=sum[p[i]];j++) 56 mul(p[i]); 57 for(int i=len;i>=1;i--) 58 printf("%d",ans[i]); 59 puts(""); 60 }
关于卡特兰数的其他应用
1.出栈入栈问题:1,2,~n个数经过一个栈,合法的出栈序列$Cat(n)$
(引用学长的课件)出栈次序是卡特兰数的一个应用。 我们将入栈视为+1,出栈视为-1,则限制条件为在任意位置前缀和不小于0 。 我们讨论这个问题与卡特兰数有什么关系。 为了方便,我们按入栈的先后顺序将各个元素由1到n编号。 假设最后一个出栈的数为k。 则在k入栈之前,比k小的数一定全部出栈,所以这部分方案数为h(k-1)。 在k入栈之后,比k大的数在k入栈之后入栈,在k出栈之前出栈,所以这部分的方案数为h(n-k)。 这两部分互不干扰,则方案数为h(k-1)*h(n-k) 枚举k,得到的公式就是卡特兰数的递推公式。
2.左括号与右括号的匹配问题:n个左括号和n个右括号组成的合法括号序列$Cat(n)$
跟入栈出栈的理解是一样的
3.n个节点构成的二叉树的方案数为$Cat(n)$
假设左子树有$i$个节点,右子树有$n-i-1$个节点,i从0到n-1,根据乘法原理
可得公式1$f(n)=\sum \limits_{i=0}^{n-1}f(i)*f(n-i-1)$