AcWing 889. 满足条件的01序列

AcWing 889. 满足条件的01序列

一、题目描述

给定 n0n1,它们将按照某种顺序排成长度为 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列有多少个。

输出的答案对 109+7 取模。

输入格式
共一行,包含整数 n

输出格式
共一行,包含一个整数,表示答案。

数据范围
1n105

输入样例

3

输出样例

5

二、卡特兰数

卡特兰数(Catalan number)是 组合数学 中一个常出现在各种 计数问题 中的 数列

数列的前几项为:1,1,2,5,14,42,132,429,1430,4862...

下面将会选取几个 经典的卡特兰问题,难度先易后难,带领同学们逐个击破解决,最后给出相关的解题模板。

1、进出栈序列

这是一道 最经典 的入门级卡特兰数题目,如果能把这题看懂,相信后面的题目也能迎刃而解。

题目描述
n 个元素 进栈序列 为:1234...n,则有多少种 出栈序列

思路
我们将进栈表示为 +1,出栈表示为 1,则 1>3>2 的出栈序列可以表示为:+11,+1,+1,1,1

根据栈本身的特点,每次出栈的时候,必定之前有元素入栈,即对于每个1前面都有一个+1 相对应。因此,出栈序列的 所有前缀和 必然大于等于 0,并且 +1 的数量 等于1 的数量。

接下来让我们观察一下 n=3 的一种出栈序列:+1,1,1,+1,1,+1。序列前三项和小于 0,显然这是个 非法的序列

如果将 第一个 前缀和小于 0 的前缀,即前三项元素都进行取反,就会得到:1,+1,+1,+1,1,+1。此时有 3+1+1 以及 311

因为这个小于 0 的前缀和必然是 1,且 1+1 多一个,取反后,1+1 少一个,则 +1 变为 n+1 个,且 1 变为 n1 个。进一步推广,对于 n个元素的每种非法出栈序列,都会对应一个含有 n+1+1 以及 n11 的序列。


解读:

  • 非法序列A 满足+11的数量一样,这样不好处理,需要转化
  • 转化思路是将第一个 前缀和1的前m项取反,得到新序列B
  • AB是一一对应的,求出B序列数量,也就是求出了A序列数量
  • 新序列B中存在一个规律,就是包含n个元素的序列,长度应该是2n,存在n+1+1,n11

Q:如何证明这两种序列是一一对应的?
假设非法序列为 A,对应的序列为 B。每个 A 只有一个 第一个前缀和小于0的前缀,所以每个 A 只能产生一个 B。而每个 B 想要还原到 A,就需要找到 第一个前缀和大于 0 的前缀,显然 B 也只能产生一个 A

每个 B 都有 n+1+1 以及 n11,因此 B 的数量为 C2nn+1 ,相当于在长度为 2n 的序列中找到n+1个位置存放 +1。相应的,非法序列的数量也就等于 C2nn+1

出栈序列的总数量共有 C2nn ,因此,合法的出栈序列的数量为C2nnC2nn+1=C2nnn+1

此时我们就得到了卡特兰数的通项 C2nnn+1 ,至于具体如何计算结果将会在后面进行介绍。

2、括号序列

题目描述
n 对括号,则有多少种 括号匹配 的括号序列

思路

左括号看成 +1,右括号看成 1,那么就和上题的进出栈一样,共有 C2nnn+1 种序列。

3、二叉树

题目描述

n+1 个叶子节点能够构成多少种形状不同的(国际)满二叉树

国际)满二叉树定义:如果一棵二叉树的结点要么是叶子结点,要么它有两个子结点,这样的树就是满二叉树。

思路

使用深度优先搜索这个 满二叉树,向左扩展时标记为 +1,向右扩展时标记为 1

由于每个非叶子节点都有两个左右子节点,所有它必然会 先向左扩展,再向右扩展。总体下来,左右扩展将会形成匹配,即变成进出栈的题型。n+1个叶子结点会有 2n 次扩展,构成 C2nnn+1 种形状不同的满二叉树。


解读:

  • 国际满二叉树与国内教材的满二叉树不一样,详细区别看 这里
  • 国际满二叉树中,如果有n+1个叶子节点,则有n个非叶子节点,每个非叶子节点引出两条边,共2n条边
  • 2n条边,应该是先左,后右,即+11和栈的那个一样,归到第一题的思路上去

4、电影购票

电影票一张 50 coin,且售票厅没有 coinm 个人各自持有 50 coinn 个人各自持有 100 coin

则有多少种排队方式,可以让每个人都买到电影票。

思路

持有 50 coin 的人每次购票时不需要找零,并且可以帮助后面持有 100 coin 的人找零;而对于持有 100 coin 的人每次购票时需要找零,但 100 coin 对后面的找零没有任何作用。

因此,相当于每个持有 100 coin 的人都需要和一个持有 50 coin 的人进行匹配。我们将持有 50 coin 的标记为 +1,持有 100 coin 的标记为 1,此时又回到了进出栈问题。

不同的是,m 并一定等于 n,且排队序列是一种排列,需要考虑先后顺序,例如各自持有 50 coin 的甲和乙的前后关系会造成两种不同的排队序列。所以,将会有 (Cm+nmCm+nm+1)m!n!

第二项为什么是 Cm+nm+1 ,其实很简单,我们每次把第一个前缀小于0 的前缀取反后,会造成 +1 多了一个而 1 少了一个。这里 +1m 个,1n 个,取反后 +1 变成m+1个,1 变成n1个,总和不变。

三、图形化推导

华罗庚:数缺形时少直观,形少数时难入微

下图表示所有在n×n格点中 不越过对角线的单调路径的个数(只向上向右走)

C2nnn+1=C84÷5=(8765)÷(54321)=14

  • 不考虑不越过对角线这个条件,有C2nn种方案

    解释:联想一下上面的1,+1的序列摆上,共2n个,然后找出n个位置使用+1

  • 对每个越过对角线的不合法方案,一定经过y=x+1这条直线,从路径与该直线的第一个交点处开始,剩余路径进行镜像处理(关于直线对称),终点一定是(n,n)关于直线的对称点(n1,n+1)
    每一条(0,0)>(n1,n+1)的路径,对应一条计数的非法路径,所以合法路径数为C2nnC2nn1

没看懂?理解一下:

假设n=6:

1、方案总数

(0,0)(6,6)共有多少种合法走法?

因为从(00)(6,6) 共需要向上共走6步,向右共走6步,一共12步。

每步两种选择,向上或向右,共2种选择。

如果在其中某6个步骤中选择向上走,那么剩下的6个步骤就是向右走。

向上走的方案数就是C126,向上的方案数定了,其它的步骤只能是向右的,最终的方案数是C126

2、灰线数量计算

每一条黑线,一旦越过红线的部分,都可以根据红线做轴对称,画出对称的灰线。这样,所有到(66)的黑线,都可以找到一条到(5,7)的灰线,换句话说,只要计算出从(0,0)(5,7)的方案数,也就是到(0,0)(6,6)的越红线(也就是黑线)的方案数个数,从(0,0)(5,7)的方案数:C125

3、合法方案数

C126C125

这是以(66)为例子,推广到n后就是 C2nnC2nn1

四、公式推导变形

C2nnC2nn1=(2n)!n!×n!(2n)!(n+1)!×(n1)!=(2n)!×(n+1)n!×(n+1)!(2n)!×nn!×(n+1)!=(2n)!×(n+1)(2n)!×nn!×(n+1)!=(2n)!n!×(n+1)!=1n+1×(2n)!n!×n!=C2nnn+1

五、取模怎么办?

C2nnC2nn1=C2nnn+1=2n×(2n1)×...×(n+1)n×(n1)×(n2)×...×1 mod(m)×inv(n+1)

=2n×(2n1)×...×(n+1)×inv(n+1)×inv(n)×inv(n1)×inv(n2)×... mod(m)

六、总结

需要注意的是,由于卡特兰数增长速度较快,当 n 等于 17 时,卡特兰数将会超过 int 最大值,造成溢出。对于 C++ 语言来说,可以使用 高精度 来计算大整数。

七、实现代码

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int p = 1e9 + 7;
int n;

int qmi(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = (LL)res * a % p;
        b >>= 1;
        a = (LL)a * a % p;
    }
    return res;
}

int main() {
    cin >> n;
    int res = 1;

    // ① 分子,2n*(2n-1)*(2n-2)*...*(n+1)
    for (int i = 2 * n; i > n; i--) res = (LL)res * i % p;
    // ② 分母,inv(n+1)*inv(n)*inv(n-1)*...*inv(1) %p
    for (int i = n + 1; i; i--) res = (LL)res * qmi(i, p - 2) % p;
    // 输出
    cout << res << endl;
    return 0;
}

八、牛刀小试

比如2016年全国三卷数学选择题压轴题让求解的就是卡特兰数,问题如下:

卡特兰数结论

C2nnn+1

题目结果:

C2mmm+1=C844+1=705=14

因此选择C

posted @   糖豆爸爸  阅读(311)  评论(1编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2019-10-10 代码战争
2017-10-10 云平台服务器应急检查步骤
2017-10-10 解决Tomcat因Redis加载慢而启动失败的问题
2017-10-10 检测磁盘挂载的方法
Live2D
点击右上角即可分享
微信分享提示