首页 > 其他 > 详细

卡特兰数总结

时间:2019-07-21 23:50:36      阅读:159      评论:0      收藏:0      [点我收藏+]

tip:

  卡特兰数是组合数学中经常出现在计数问题的数列,出栈次序是卡特兰数的一个应用。 我们将入栈视为 +1,出栈视为 -1,则限制条件为在任意位置前缀和不小于 0。

  卡特兰数公式:

技术分享图片

技术分享图片

技术分享图片

技术分享图片

 技术分享图片

  卡特兰数前几项为: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。

实战:

T1:「BZOJ3907」网格

题干:

  某城市的街道呈网格状,左下角坐标为 A(0,0),右上角坐标为 B(n,m),其中 n≥m。现在从 A(0,0) 点出发,只能沿着街道向正右方或者正上方行走,且不能经过图示中直线左上方的点,即任何途径的点 (x,y) 都要满足 x≥y,请问在这些前提下,到达 B(n,m) 有多少种走法。

技术分享图片

题解:

  一道 catalan 裸题。但是 n 不一定等于 m,所以我们就需要变换一下 catalan 的式子。catalan 的定义式就是两个组合数相减的形式,是一个单步容斥。

技术分享图片

  看一下上图,我们单步容斥减去的就是不合法的情况(紫框)。不合法的情况一定会碰到黄线,我们可以将碰到黄线以后的部分进行沿黄线翻折,这个翻折后的路径一定在紫框内,减去即可。即:

$ans=C_{n+m}^{n}-C_{n+m}^{m-1}$

  (注意:高精减时注意开头的 0,高精乘低精时余数应是long long)

Code:

技术分享图片
 1 #include<cstdio>
 2 #include<cstring>
 3 #define $ 101010
 4 #define ll long long
 5 using namespace std;
 6 int c1[$],c2[$],a1[$/10],a2[$/10],m,n,max1,max2,pr[$/10];
 7 inline int max(int x,int y){    return x>y?x:y;    }
 8 inline void cut1(int x,int add){
 9     for(register int i=2;i*i<=x;++i)
10         while((x%i)==0) x/=i,c1[i]+=add,max1=max(max1,i);
11     if(x>1) c1[x]+=add,max1=max(max1,x);
12 }
13 inline void cut2(int x,int add){
14     for(register int i=2;i*i<=x;++i)
15         while((x%i)==0) x/=i,c2[i]+=add,max2=max(max2,i);
16     if(x>1) c2[x]+=add,max2=max(max2,x);
17 }
18 inline void mul1(int yu=0){
19     a1[0]=a1[1]=1;
20     for(register int i=2;i<=max1;++i){
21         if(c1[i]==0) continue;
22         for(register int j=1;j<=c1[i];++j){
23             for(register int k=1,tmp=i;k<=a1[0];++k){
24                 a1[k]=a1[k]*tmp+yu;
25                 yu=a1[k]/10;
26                 a1[k]%=10;
27             }
28             while(yu)    a1[++a1[0]]=yu%10, yu/=10;
29         }
30     }
31 }
32 inline void mul2(int yu=0){
33     a2[0]=a2[1]=1;
34     for(register int i=2;i<=max1;++i){
35         if(c2[i]==0) continue;
36         for(register int j=1,tmp=i;j<=c2[i];++j){
37             for(register int k=1;k<=a2[0];++k){
38                 a2[k]=a2[k]*tmp+yu;
39                 yu=a2[k]/10;
40                 a2[k]%=10;
41             }
42             while(yu)    a2[++a2[0]]=yu%10, yu/=10;
43         }
44     }
45 }
46 inline void del(){
47     ll tmp=1,yu=0;
48     while(tmp<=a1[0]||tmp<=a2[0]){
49         if(a1[tmp]<a2[tmp]){
50             a1[tmp+1]--; yu=10;
51         }
52         pr[tmp]=a1[tmp]-a2[tmp]+yu;  yu=0;
53         tmp++;
54     }
55     while(pr[tmp]==0&&tmp>1) tmp--;
56     pr[0]=tmp;
57     for(register int i=pr[0];i>=1;--i) printf("%d",pr[i]);
58     puts("");
59 }
60 signed main(){        
61     scanf("%d%d",&n,&m);
62     for(register int i=n+1;i<=n+m;++i) cut1(i,1);
63     for(register int i=2;i<=m;++i)     cut1(i,-1);
64     for(register int i=n+2;i<=n+m;++i) cut2(i,1);
65     for(register int i=2;i<=m-1;++i)   cut2(i,-1);
66     mul1(); mul2(); del();
67 }
View Code

 

T2:「HNOI 2009」有趣的数列

题干:

  我们称一个长度为 2n 的数列是有趣的,当且仅当该数列满足以下三个条件:
    1、它是从 1 到 2n 共 2n 个整数的一个排列 {ai};
    2、所有的奇数项满足 $a_1<a_3<?<a_{2n−1}$,所有的偶数项满足 $a_2<a_4<?<a_{2n}$;
    3、任意相邻的两项 $a_{2i−1}$?? 与 $a_{2i}$ (1≤i≤n) 满足奇数项小于偶数项,即:$a_{2i−1}<a_{2i}$
    对于给定的 n,请求出有多少个不同的长度为 2n 的有趣的数列,输出答案 mod P 的值。
题解:

  题目可化简为: 把所有数分成两组A、B,且排序后 Ai 始终比 Bi 小的方案数
  很像卡特兰数的模型,我们把 1~2n 写出来,显然可以有顺序地向里填,对于一个数 i,要么填 A 要么填 B。那么这个时候 A 集合的大小一定要小于 B 集合。这就是一个卡特兰数了。

  本题其实就是从左往右扫每个数,把放在奇数项看作入栈,偶数看作出栈,符合 $catalan$ 使用模型。

  (注意本题若只用唯一分解定理,会 T 得很惨。。。需要写一个线性筛,将每一个数的最小质因子预处理一下)

线筛:

 1 inline void eular(){
 2     lst[1]=1;
 3     for(register int i=2;i<=2*n;++i){
 4         if(judge[i]==0) prime[++cnt]=i, lst[i]=i;
 5         for(register int j=1;j<=cnt&&1ll*i*prime[j]<=2ll*n;++j){
 6             judge[i*prime[j]]=1;
 7             lst[i*prime[j]]=prime[j];
 8             if(i%prime[j]==0) break;
 9         }
10     }
11 }

Code:

技术分享图片
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cstdlib>
 4 #define $ 2100010
 5 #define ll long long
 6 using namespace std;
 7 int c[$],n,lst[$],prime[$],cnt,maxx;
 8 ll ans=1,mod;
 9 bool judge[$];
10 inline int max(int x,int y){    return x>y?x:y;    }
11 inline ll modd(ll x){    return x-(x/mod*mod);    }
12 inline void cut(int x,int add){
13     while(x!=1)    maxx=max(maxx,lst[x]), c[lst[x]]+=add, x/=lst[x];
14 }
15 inline void eular(){
16     lst[1]=1;
17     for(register int i=2;i<=2*n;++i){
18         if(judge[i]==0) prime[++cnt]=i, lst[i]=i;
19         for(register int j=1;j<=cnt&&1ll*i*prime[j]<=2ll*n;++j){
20             judge[i*prime[j]]=1;
21             lst[i*prime[j]]=prime[j];
22             if(i%prime[j]==0) break;
23         }
24     }
25 }
26 inline int qpow(ll a,int x,ll sum=1){
27     for(;x;x>>=1,a=a*a%mod) if(x&1) sum=modd(sum*a);
28     return (int)sum;
29 }
30 signed main(){
31     scanf("%d%lld",&n,&mod);
32     eular();
33     //for(register int i=1;i<=n;++i) printf("%d %d\n",i,lst[i]);
34     for(register int i=n+2;i<=n*2;++i) cut(i,1);
35     for(register int i=2;i<=n;++i)     cut(i,-1);
36     for(register ll i=2;i<=maxx;++i) 
37         if(c[i]>0) ans=modd(ans*qpow(i,c[i]));
38     printf("%lld\n",ans);
39 }
View Code

 

T3:「AHOI 2012」树屋阶梯

题干:

  暑假期间,小龙报名了一个模拟野外生存作战训练班来锻炼体魄,训练的第一个晚上,教官就给他们出了个难题。由于地上露营湿气重,必须选择在高处的树屋露营。小龙分配的树屋建立在一颗高度为 N+1 尺的大树上,正当他发愁怎么爬上去的时候,发现旁边堆满了一些空心四方钢材,经过观察和测量,这些钢材截面的宽和高大小不一,但都是 1 尺的整数倍,教官命令队员们每人选取 N 个空心钢材来搭建一个总高度为 N 尺的阶梯来进入树屋,该阶梯每一步台阶的高度为 1 尺,宽度也为 1 尺。如果这些钢材有各种尺寸,且每种尺寸数量充足,那么小龙可以有多少种搭建方法?(为了避免夜里踏空,钢材空心的一面绝对不可以向上)

题解:

  显然每个凸出来的角各自都是一块,只有一个是覆盖了左下角的,那么就会把这个阶梯分成两个阶梯子问题。就有:

$f[n]=f[0]*f[n-1]+f[1]*f[n-2]+…f[n-1]*f[0]$

  我们还可以令 $C_n$ 表示用 n 个长方形拼成 $size$ 为 $n$ 的三角梯形的方案数。
我们枚举最左下角的点属于哪个长方形。显然有n种可能,每种方法又把剩下的部分分成两个三角梯形( $size$ 可能为 0 )
我们可以得到:

$Catalan_n = \sum_{i = 0}^{n-1} Catalan_i * Catalan_{n-i-1}$

  标准 $Catalan$ 方程。

Code:

技术分享图片
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cstdlib>
 4 #define $ 1055
 5 #define ll long long
 6 using namespace std;
 7 int m,n,k,t,c[$],maxx,prime[$],lst[$],cnt,a[$];
 8 bool judge[$];
 9 inline int max(int x,int y){    return  x>y?x:y;    }
10 inline void cut(int x,int add){
11     while(x!=1)  maxx=max(maxx,lst[x]),c[lst[x]]+=add, x/=lst[x];
12 }
13 inline void eular(){
14     lst[1]=1;
15     for(register int i=2;i<=n*2;++i){
16         if(!judge[i]) prime[++cnt]=i, lst[i]=i;
17         for(register int j=1;j<=cnt&&i*prime[j]<=n*2;++j){
18             judge[i*prime[j]]=1;
19             lst[i*prime[j]]=prime[j];
20             if(i%prime[j]==0) break;
21         }
22     }
23 }
24 inline void mul(int yu=0){
25     a[0]=a[1]=1;
26     for(register int i=2;i<=maxx;++i){
27         while(c[i]--){
28             for(register int j=1;j<=a[0];++j){
29                 a[j]=a[j]*i+yu;
30                 yu=a[j]/10;
31                 a[j]%=10;
32             }
33             while(yu) a[++a[0]]=yu%10, yu/=10;
34         }
35     }
36     for(register int i=a[0];i>=1;--i) printf("%d",a[i]);
37 }
38 signed main(){
39     scanf("%d",&n); eular();
40     for(register int i=n+2;i<=n*2;++i) cut(i,1);
41     for(register int i=2;i<=n;++i)     cut(i,-1);
42     mul();
43 }
View Code

 

卡特兰数总结

原文:https://www.cnblogs.com/OI-zzyy/p/11222728.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!