卡特兰数相关总结

一、前置知识

在笛卡尔坐标系中,起点为\((0,0)\),终点为\((n,m)\),每一步只能向上或向右走的方案数为\(C_{n+m}^n\)

二、基本卡特兰数

首先亮出卡特兰数的基本公式:

\[Cat_n = \frac{C_{2n}^n}{n + 1}\]

那么卡特兰数有许多经典问题,我们来从最经典的入手。

例一:在平面直角坐标系中,起点为\((0,0)\),终点为\((n,n)\),第一步只能向上走。在之后的移动中可以向上走或向右走,但不能超出直线\(y=x\) 。求方案数。

首先这个方案数可以表示为从(0,0)走到(n,n)的总方案数减去至少超出一次的方案数。

从(0,0)走到(n,n)的总方案数即为\(C_{2n}^n\).

现在考虑至少超出一次的方案数。当我们刚刚超出直线\(y = x\)的时候,我们一定处于直线\(y = x - 1\)上,把此时所在的点记为点A。现在我们做这样一件事情,把原本从点A到终点的路径围绕直线\(y = x - 1\)翻折,那么这条路径就变成从原点到点\((n+1,n-1)\)的一条路径。

我们又可以发现,所有违法的路径与所有从原点到\((n + 1, n - 1)\)的路径构成一一对应关系。证明极其简单,在脑子里想一下就行了。

所以至少超出一次的方案数就是\(C_{(n+1)+(n-1)}^{n+1}=C_{2n}^{n+1}\)

最终的答案即为\(C_{2n}^n - C_{2n}^{n+1}\)。化简后得到\(\frac{C_{2n}^n}{n + 1}\)

三、扩展卡特兰数

例二:其他条件与例一相同,唯一不一样的是把直线\(y = x\) 改为直线\(y = x - m\)\((m\geq 1)\)

有了刚才的思路,我们很容易想到把违法的路径从点A到终点的部分围绕直线\(y = x - m - 1\)翻折,可以得到从原点到\((n - m - 1, n +m + 1)\)的路径。同样,两者构成一一对应关系。

最终的答案即为\(C_{2n}^{n} - C_{2n}^{n - m - 1}\)

四、应用

看一道真实的考试题。

如果我们把左括号看成上文例题中的“向上走一步”,右括号看成“向右走一步”,我们会发现本题中所说的“至少有\(2\times m\)个括号失配”就是“在走的过程中,接触过直线\(y=x-m\)的一条路径”。注意:是“接触过直线\(y=x-m\)”,不能超出,也不能不接触。

所以方案数就应该为“不超过\(y=x- m\)”减去“不超过\(y=x-m-1\)

代码:

#include<cstdio>
using namespace std;
const int maxn=2001000;
const long long md=998244353;
long long fac[maxn],inv[maxn];
long long powd(long long x,int y){
    long long ret=1;
    x%=md;
    while(y){
        if(y&1) ret=ret*x%md;
        x=x*x%md;
        y>>=1;
    }
    return ret;
}
long long C(int x,int y){
    if(x<y) return 0;
    if(y<0||x<0) return 0;
    return fac[x]*inv[y]%md*inv[x-y]%md;
}
long long excatalan(int x,int y){
    return (C(x*2,x)-C(x*2,x-y-1)+md)%md;//核心代码
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    fac[0]=1;
    for(int i=1;i<=n*2;i++)
        fac[i]=fac[i-1]*i%md;
    inv[n*2]=powd(fac[n*2],md-2);
    for(int i=n*2;i>=1;i--)
        inv[i-1]=inv[i]*i%md;
    if(m==0)
        printf("%lld\n",excatalan(n,0));
    else
        printf("%lld\n",(excatalan(n,m)-excatalan(n,m-1)+md)%md);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/little-aztl/p/11763966.html