欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-13 11:55:09
...

一、关于卡特兰数

卡特兰数是一种经典的组合数,经常出现在各种计算中,其前几项为 : 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。
卡特兰的应用:洛谷P1044栈;P1976鸡蛋饼

那么一个足够大的栈的进栈序列为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(将一个元素进栈)。

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

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

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

现在可以进行两种操作,

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

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

(原始状态如上图所示)

你的程序将对给定的 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 个点的情形 在这里插入图片描述
卡特兰的应用:洛谷P1044栈;P1976鸡蛋饼

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

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

这一种情形下的切法数为 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;
}

相关标签: 题目