卡特兰的应用:洛谷P1044栈;P1976鸡蛋饼

一、关于卡特兰数

卡特兰数是一种经典的组合数,经常出现在各种计算中,其前几项为 : 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(0)=1,h(1)=1,catalan数满足递推式。h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0) (n>=2)。也就是说,如果能把公式化成上面这种形式的数,就是卡特兰数。

三、卡特兰的应用
1、进出栈问题:栈是一种先进后出(FILO,First In Last Out)的数据结构.如下图1,1,2,3,4顺序进栈,那么一种可能的进出栈顺序是:1In→2In→2Out→3In→4In→4Out→3Out→1Out, 于是出栈序列为1,3,4,2。
在这里插入图片描述

那么一个足够大的栈的进栈序列为1,2,3,⋯,n时有多少个不同的出栈序列?

**我们可以这样想,假设k是最后一个出栈的数。比k早进栈且早出栈的有k-1个数,一共有h(k-1)种方案。比k晚进栈且早出栈的有n-k个数,一共有h(n-k)种方案。所以一共有h(k-1)*h(n-k)种方案。显而易见,k取不同值时,产生的出栈序列是相互独立的,所以结果可以累加。k的取值范围为1至n,所以结果就为h(n)= h(0)h(n-1)+h(1)h(n-2) + … + h(n-1)h(0)

题目背景
栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。

栈有两种最重要的操作,即 pop(从栈顶弹出一个元素)和 push(将一个元素进栈)。

栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。

题目描述
在这里插入图片描述

宁宁考虑的是这样一个问题:一个操作数序列,1,2,\ldots ,n1,2,…,n(图示为 1 到 3 的情况),栈 A 的深度大于 nn。

现在可以进行两种操作,

将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的 push 操作)
将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的 pop 操作)
使用这两种操作,由一个操作数序列就可以得到一系列的输出序列,下图所示为由 1 2 3 生成序列 2 3 1 的过程。

在这里插入图片描述

(原始状态如上图所示)

你的程序将对给定的 nn,计算并输出由操作数序列 1,2,\ldots,n1,2,…,n 经过操作可能得到的输出序列的总数。

输入格式
输入文件只含一个整数 nn(1 \leq n \leq 181≤n≤18)。

输出格式
输出文件只有一行,即可能输出序列的总数目。

输入输出样例
输入 #1复制
3
输出 #1复制
5

#include<iostream>
using namespace std;
int main()
{
    int n,f[30];
    cin>>n;
    f[0]=f[1]=1;
    for(int i=2; i<=n; i++)
    {
        f[i]=0;
        for(int j=0; j<i; j++)
        {
            f[i]+=f[j]*f[i-j-1];
        }
    }
    cout<<f[n];
    return 0;
}

2、在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?(答案卡特兰数)

题目背景
Czyzoiers 都想知道小 x 为什么对鸡蛋饼情有独钟。经过一番逼问,小 x 道出了实情:因为他喜欢圆。

题目描述
最近小 x 又发现了一个关于圆的有趣的问题:在圆上有 2N2N 个不同的点,小 x 想用 N 条线段把这些点连接起来(每个点只能连一条线段), 使所有的线段都不相交,他想知道这样的连接方案有多少种?

输入格式
有且仅有一个正整数 N 。 (N \le 2999N≤2999)

输出格式
要求的方案数(结果 \bmod 100000007mod100000007)。

输入输出样例
输入 #1复制
24
输出 #1复制
4057031

题目分析
特例分析
我们先来考虑 4 个点的情形 在这里插入图片描述
在这里插入图片描述

显然有以下 2 种方案:[AD,BC],[AB,CD].

再考虑一下 6个点的情形
其实由于连线不会相互交叉,每一次连线,相当于在饼上切了一刀,将饼分为两部分,我们可以照着操作一下。
首先假设我们第一刀切 BE,那么这块饼分成了两部分,一部分上还有4个点可供切割,另一部分0个点可供切割,而由于 BEBE 已经被切过,这两个点接下来应该不计入考虑,事实上我们把问题分解成了两部分,即圆上4个点和圆上0个点的情形. 在这里插入图片描述
在这里插入图片描述

这一种情形下的切法数为 2\times 1=2×1=2 种.

如果第一步沿 EC 或者 ED 切,则连不满 NN 根线.

如果第一步沿 EA切,则切法仍为2\times 1=2×1=2 种.

如果第一步沿 EF切,显然只剩下 1 种.

显然我们已经算完了所有不重复的结果,因此 66个点时有 55种方法.

一般情况
那么如果有 2N 个点呢?

根据以上我们得出,如果第一刀,使得分成的两半上均只有奇数个点,将连不满 NN根线,换言之,此时满足要求的切法为 0.

假设,圆上有 2k 个点时,切法为 f(k),那么根据第一刀分成的左右两边的点的个数,由加法原理以及乘法原理可以得到

f(N+1)=f(0)f(N)+f(1)f(N-1)+\dotsm+f(N)f(0).
f(N+1)=f(0)f(N)+f(1)f(N−1)+⋯+f(N)f(0).
那么既然初始点的选择是任意的,我们是否需要为此乘上 2N 或者 N之类的系数呢?

我们还是回到 6 个点的分析中去,倘若我们选择 BC 作为第一刀,结果又将如何呢?答案是结果完全一样,无论是数量还是切法,都将保持一致.

我们以集合的角度进行思考,一种切法中的所有弦构成一个集合,所有的切法集合构成全集 U,所以其实定弦是一种遍历的方式,首先圆上所有的点都要被连到,那也就意味着所有的切法里面必然含有所有点,那么如果我们选定一个有特征的点 A,作为起始点,这个起始点是非特异的,而作为 A 这个点而言,和周围的点也只有 N种连接方式,我们考虑了这N 种连接方式的全体,相当于是对这个集合做了一次遍历,既然是遍历,也即意味着选一个点即可计算出全体,而不是一种特例,所以不用乘以任何系数。

#include<iostream>
using namespace std;
int main()
{
   unsigned long long int ctl[3000]={0},i,j,n;
    ctl[0]=ctl[1]=1,ctl[2]=2;
    cin>>n;
    for(i=3;i<=n;i++)
    {
        for(j=0;j<i;j++)
        {
            ctl[i]+=ctl[j]*ctl[i-1-j];
            ctl[i]%=100000007;
        }
    }
    cout<<ctl[n]<<endl;
    return 0;
}

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页