• 卡特兰数


    $Catalan$

      今天跟着$asuldb$复习了一下组合数学,发现$Catalan$数一直不是很明白,那就再学习一下吧.

      关于卡特兰数的题目有一种特别好用的方法,首先打表/手玩几组解,如果看起来像卡特兰...那就用吧!$1, 2, 5, 14, 42, 132$

      卡特兰数是比利时数学家卡特兰发明的,有几个非常经典的应用:

      1 出栈序列:将$1-n$依次加入到一个无穷大的栈里面,可以随时出栈,求有多少种出栈序列.

      可以将这道题抽象一下,进栈为$1$,出栈为$-1$,那么出栈序列就是一个由$1,-1$组成的序列,这里面有$n$个$1$,所以答案就是$C_{2n}^n$...这样做是错的...

      因为每次出栈的时候栈不能是空的,所以对序列做一个前缀和,必须每个前缀和都非负才满足条件。对于一个不合法的序列,首先找到它第一个不合法的位置,前缀和一定等于-1,然后将以他结尾的前缀取反,这样就会有$n+1$个$1$了,此时可以发现由$n+1$个$1$,$n-1$个$0$构成的序列与不合法序列是一一对应的(找到前缀和第一次为$1$的位置取反就可以回到原先的不合法序列),这样的方案数是$C_{2n}^{n-1}$,所以总的答案就是$C_{2n}^n-C_{2n}^{n-1}$

      1.5 长度为$2n$的合法括号序列计数:左括号视为进栈,右括号视为出栈,同上.

      2.二叉树形态计数:一个简单的树形$dp$,$f_x=sum_{i=0}^{x-1}f_i imes f_{x-i-1}$.令人惊讶的是这竟然就等于卡特兰数;

      3.$n+2$个顶点的凸多边形的三角剖分计数:这个和二叉树比较像,也是考虑剖分一次后就将多边形分成了两个部分,乘法原理+加法原理;

      4.棋盘上走路,不越过$(1,1)-(n,n)$这条线的走法:考虑将向右走视为加一,向上走视为减一,那么任意时间不为负...和第一个是一样的.

      5.圆上$n$个点,两两配对并连线,要求连线不能交叉的方案数:首先固定一个点,有$n-1$种连线方法,其中每一种都将圆分成了两个部分,还是和二叉树那个差不多.

     

      那么卡特兰数有好几个计算公式,下面来写一下:

      $$C_n=sum_{i=0}^{n-1}C_i imes C_{x-i-1}$$

      $$C_n=inom {n}{2n}-inom{n-1}{2n}$$

      $$C_n=frac{inom {n}{2n}}{n+1}$$

      $$C_n=C_{n-1} imes frac{4 imes n-2}{n+1}$$

      不过这第四个公式真的有用吗...

      然而仅仅考卡特兰数太没意思了,出题人往往想出一些奇奇怪怪的方法提高难度:

      $Part$ $1$:你知道这就是求卡特兰数但是就是求不出来系列:

      取模:

      取模与高精相比看起来是很良心的一种题目,比如说这个:

      有趣的数列:https://www.lydsy.com/JudgeOnline/problem.php?id=1485

      题意概述:求满足以下条件且长度为$2n$的序列个数$\%P$:$n<=10^6且P<=10^9$

      (1)它是从$1$到$2n$共$2n$个整数的一个排列${a_i}$;

      (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$这么大,那么肯定是不能用第一个公式了,于是你愉快地运用了第二个公式并使用快速幂求阶乘逆元,然后$WA$了.

      这时才发现$P$不一定是质数,所以有时候做着做着答案就乱套了(样例都过不了).于是这里要运用一个类似于高精度的技巧,首先最终的答案肯定是一个整数,那么如果运用第三个公式就不用担心除法出现小数的问题了.这就是问题的关键所在:分子上的数的唯一分解式中每一项的系数都不小于分母,所以可以对分子分母分别分解质因数,直接求出最终答案的唯一分解式.因为乘法取模对模数没有限制,就可以做了.

      关于分解质因数还有一点小建议:如果对效率没有很高的追求,可以用根号算法分解,如果对效率有着极致的追求,那么可以利用线性筛法的性质,在筛的同时记录每个数的最小质因子,每次除以最小质因子,复杂度约为$logN$.

      
     1 # include <cstdio>
     2 # include <iostream>
     3 # include <cstring>
     4 # include <string>
     5 # include <algorithm>
     6 # include <cmath>
     7 # define R register int
     8 # define ll long long
     9 
    10 using namespace std;
    11 
    12 const int maxn=2000006;
    13 int n,p,vis[maxn],pri[maxn],h,z[maxn],c[maxn];
    14 ll ans=1,f[maxn];
    15 
    16 ll qui (ll a,ll b,ll p)
    17 {
    18     ll s=1;
    19     while(b)
    20     {
    21         if(b&1) s=s*a%p;
    22         a=a*a%p;
    23         b>>=1;
    24     }
    25     return s%p;
    26 }
    27 
    28 void ad (int x,int v)
    29 {
    30     while(vis[x])
    31     {
    32         c[ z[x] ]+=v;
    33         x/=z[x];
    34     }
    35     c[x]+=v;
    36 }
    37 
    38 void init (int x)
    39 {
    40     for (R i=2;i<=x;++i)
    41     {
    42         if(!vis[i]) pri[++h]=i;
    43         for (R j=1;j<=h&&i*pri[j]<=x;++j)
    44         {
    45             vis[ i*pri[j] ]=1;
    46             z[ i*pri[j] ]=pri[j];
    47             if(i%pri[j]==0) break;
    48         }
    49     }
    50 }
    51 
    52 int main()
    53 {
    54     scanf("%d%d",&n,&p);
    55     init(2*n);
    56     for (R i=2;i<=2*n;++i) ad(i,1);
    57     for (R i=2;i<=n;++i) ad(i,-1);
    58     for (R i=2;i<=n+1;++i) ad(i,-1);
    59     for (R i=2;i<=2*n;++i)
    60         if(c[i]) ans=(ans*qui(i,c[i],p))%p;
    61     printf("%lld",ans);
    62     return 0;
    63 }
    有趣的数列

      还有的题目就比较凉心了,(为了让你免去取模的麻烦),干脆不取模了.

      树屋阶梯:https://www.lydsy.com/JudgeOnline/problem.php?id=2822

      一道题意不好概括的题目.

      考虑使用二叉树的分析方式,首先将阶梯从某一层分开,那么上面就是$C_i$,下面就是$C_{n-i}$,看起来非常有道理,然而是错误的,举个例子:

      

      这样的阶梯可能在好几种分层中被认为是多种方案...

      那么换一种分层方式:

      

      也就是说上下两个子阶梯的大小加起来正好比总大小小$1$,这个绿色的大块直接用一块填上,蓝,红两部分还是$C_i imes C_{n-i-1}$,这样的好处是一定不重复.

      高精度的问题其实没有想象中那么难做,依旧沿用上一题的思想,分解完质因数后只需要一个高精度快速幂或朴素乘法即可,乘法相对还是比较简单的.

      
     1 # include <cstdio>
     2 # include <iostream>
     3 # define maxn 100005
     4 # define R register int
     5 # define ll long long
     6 
     7 using namespace std;
     8 
     9 ll t,cat[maxn*100];
    10 int n,a,u[maxn],d[maxn];
    11 
    12 void print ()
    13 {
    14     int len=cat[0];
    15     for (R i=len;i>=1;--i)
    16         printf("%lld",cat[i]);
    17 }
    18 
    19 void mul (ll x)
    20 {
    21     int w=0;
    22     for (R i=1;i<=cat[0];++i)
    23     {
    24         cat[i]*=x;
    25         cat[i]+=w;
    26         w=cat[i]/10;
    27         cat[i]%=10;
    28     }
    29     while (w)
    30     {
    31         cat[ ++cat[0] ]+=w;
    32         w=cat[ cat[0] ]/10;
    33         cat[ cat[0] ]%=10;
    34     }
    35 }
    36 
    37 ll qui (int a,int b)
    38 {
    39     ll s=1;
    40     while (b)
    41     {
    42         if(b&1LL) s*=a;
    43         a*=a;
    44         b>>=1LL;
    45     }
    46     return s;
    47 }
    48 
    49 int main()
    50 {
    51     scanf("%d",&n);
    52     for (R i=2;i<=n;++i)
    53     {
    54         a=n+i;
    55         for (R j=2;j*j<=a;++j)
    56             while (a%j==0) a/=j,u[j]++;
    57         if(a>1) u[a]++;
    58         a=i;
    59         for (R j=2;j*j<=a;++j)
    60             while (a%j==0) a/=j,d[j]++;
    61         if(a>1) d[a]++;
    62     }
    63     cat[0]=cat[1]=1;
    64     for (R i=2;i<=2*n;++i)
    65     {
    66         if(!u[i]) continue;
    67         u[i]-=d[i];
    68         if(!u[i]) continue;
    69         t=qui(i,u[i]);
    70         if(t!=1) mul(t);
    71     }
    72     print();
    73     return 0;
    74 }
    树屋阶梯

       

      $Part$ $2$:根本看不出来是卡特兰数系列:

      其实严格来讲这几道题确实不是卡特兰数,但是推式子的思想有一些相似之处:

      生成字符串:https://www.lydsy.com/JudgeOnline/problem.php?id=1856

      题意概述:将$n$个$1$,$m$个$0$组成一个字符串,要求任意前缀中$1$的个数不能少于$0$的个数,求方案数.

      其实这题不算非常难想,但如果对于卡特兰数的式子不明白本质的话就很难做了.

      依旧是找到第一个不满足条件的位置翻转,嗯,没了.$C_{n+m}^m-C_{n+m}^{m-1}$,这题挺良心的,对质数取模.

      
     1 # include <cstdio>
     2 # include <iostream>
     3 # define R register int
     4 # define mod 20100403
     5 
     6 const int maxn=2000006;
     7 int f[maxn];
     8 int n,m,ans;
     9 
    10 int inv (int a)
    11 {
    12     int s=1,b=mod-2;
    13     while(b)
    14     {
    15         if(b&1) s=1LL*s*a%mod;
    16         a=1LL*a*a%mod;
    17         b>>=1;
    18     }
    19     return s%mod;
    20 }
    21 
    22 int C (int n,int m)
    23 {
    24     return 1LL*f[n]*inv(f[m])%mod*inv(f[n-m])%mod;
    25 }
    26 
    27 int main()
    28 {
    29     scanf("%d%d",&n,&m);
    30     n+=m;
    31     f[0]=1;
    32     for (R i=1;i<=n;++i)
    33         f[i]=1LL*f[i-1]*i%mod;
    34     ans=((C(n,m)-C(n,m-1))%mod+mod)%mod;
    35     printf("%d",ans);
    36     return 0;
    37 }
    生成字符串

       

      网格:https://www.lydsy.com/JudgeOnline/problem.php?id=3907

      题意概述:在一个$n imes m$的网格里往上或往右走,不能超过$(1,1)-(n,n)$这条线,求方案数;$n,m<=5000$

      其实还是很水的,依旧考虑一样的翻转做法,将右走视为$1$,上走视为$-1$.不过这道题让人有点难受,又没有取模,高精度.$C_{n+m}^{n}-C_{n+m}^{n+1}$

      
     1 # include <cstdio>
     2 # include <iostream>
     3 # include <cstring>
     4 # define R register int
     5 
     6 using namespace std;
     7 
     8 const int maxn=10009;
     9 int n,m;
    10 int pri[10009];
    11 int a[10000],b[10000];
    12 
    13 void print()
    14 {
    15     for (R i=a[0];i>=1;--i)
    16         printf("%d",a[i]);
    17 }
    18 
    19 void ad (int x,int v)
    20 {
    21     for (R i=2;i*i<=x;++i)
    22         while (x%i==0) pri[i]+=v,x/=i;
    23     if(x!=1) pri[x]+=v; 
    24 }
    25 
    26 void mul1 (int n)
    27 {
    28     for (R i=1;i<=a[0];++i)
    29         a[i]*=n;
    30     for (R i=1;i<=a[0]+5;++i)
    31         a[i+1]+=a[i]/10,a[i]%=10;
    32     a[0]+=5;
    33     while (a[ a[0] ]==0&&a[0]) a[0]--;
    34 }
    35 
    36 void mul2 (int n)
    37 {
    38     for (R i=1;i<=b[0];++i)
    39         b[i]*=n;
    40     for (R i=1;i<=b[0]+5;++i)
    41         b[i+1]+=b[i]/10,b[i]%=10;
    42     b[0]+=5;
    43     while (b[ b[0] ]==0&&b[0]) b[0]--;
    44 }
    45 
    46 void sub()
    47 {
    48     for (R i=1;i<=a[0];++i)
    49         if(a[i]>=b[i]) a[i]-=b[i];
    50         else
    51         {
    52             a[i+1]--;
    53             a[i]=a[i]+10-b[i];
    54         }
    55     while (a[ a[0] ]==0&&a[0]) a[0]--;
    56 }
    57 
    58 int main()
    59 {
    60     scanf("%d%d",&n,&m);
    61     a[0]=a[1]=b[0]=b[1]=1;
    62     for (R i=2;i<=n+m;++i)
    63         ad(i,1);
    64     for (R i=2;i<=m;++i)
    65         ad(i,-1);
    66     for (R i=2;i<=n;++i)
    67         ad(i,-1);
    68     for (R i=2;i<=10000;++i)
    69     {
    70         if(!pri[i]) continue;
    71         for (R j=1;j<=pri[i];++j)
    72             mul1(i);        
    73     }
    74     memset(pri,0,sizeof(pri));
    75     for (R i=2;i<=n+m;++i)
    76         ad(i,1);
    77     for (R i=2;i<=m-1;++i)
    78         ad(i,-1);
    79     for (R i=2;i<=n+1;++i)
    80         ad(i,-1);
    81     for (R i=2;i<=10000;++i)
    82     {
    83         if(!pri[i]) continue;
    84         for (R j=1;j<=pri[i];++j)
    85             mul2(i);
    86     }
    87     sub();
    88     print();
    89     return 0;
    90 }
    网格

    ---shzr

  • 相关阅读:
    关于博客园各项工具的使用
    Java常用的7大排序算法汇总
    Java 基本数据类型(新手必看资料)
    学习Java,还需要学好哪些知识
    JavaSE基础知识总结
    python2.7.11安装pygame包
    phpstorm打开项目目录时,出现一直在扫描文件
    laravel 通过npm搭建前端资源的注意事项
    基于laravel5.2进行rabbitmq队列服务发送接收信息
    在centos7中安装composer
  • 原文地址:https://www.cnblogs.com/shzr/p/9911433.html
Copyright © 2020-2023  润新知