火车进出栈问题 题解

来源 卡特兰数

个人评价(一句话描述对这个题的情感)

…~%?..,# *'☆&℃$︿★?

1 题面

一列火车n节车厢,依次编号为1,2,3,…,n。每节车厢有两种运动方式,进栈与出栈,问n节车厢出栈的可能排列方式有多少种。

输入

一个数,n(n<=60000)

输出

一个数s表示n节车厢出栈的可能排列方式

样例

样例输入1

3

样例输出1

5

2 分析题面

求卡特兰数的第n项,不取模

有两种方式,都能推出这是一道纯卡特兰数

2.1 递推

计数原理中的乘法原理,总的方案数等于第一步的方案数和第二步的方案数之积

以编号为 k k k 的车厢为界,将列车分为两部分

一部分是在第 k k k 节车厢出栈前出栈的车厢,一部分是在第 k k k 节车厢出栈后出栈的车厢

设在第 k k k节车厢出栈前出栈的车厢的数量为 i ( 0 < = i < = k ) i(0<=i<=k) i(0<=i<=k)

则在第 k k k 节车厢出栈后出栈的车厢的数量为 ( k − i − 1 ) (k-i-1) (ki1)

i i i 节车厢出栈的可能性数量有 F ( i ) F(i) F(i)

( n − i − 1 ) (n-i-1) (ni1) 节车厢出栈的可能性数量有 F ( k − i − 1 ) F(k-i-1) F(ki1)

所以总的数量为 F ( i ) × F ( k − i − 1 ) F(i)\times F(k-i-1) F(i)×F(ki1)

由于当只有0节车厢(即一节车厢也没有)时,方案数为 1 1 1

所以, F ( 0 ) = 1 F(0)=1 F(0)=1

因此,我们可以得到一个递归公式:

F ( k ) = F ( 0 ) × F ( k − 1 ) + F ( 1 ) × F ( k − 2 ) + F ( 2 ) × F ( k − 3 ) + . . . + F ( k − 2 ) × F ( 1 ) + F ( k − 1 ) × F ( 0 ) F(k)=F(0)\times F(k-1)+F(1)\times F(k-2)+F(2)\times F(k-3)+...+F(k-2)\times F(1)+F(k-1)\times F(0) F(k)=F(0)×F(k1)+F(1)×F(k2)+F(2)×F(k3)+...+F(k2)×F(1)+F(k1)×F(0)

其中 k > = 1 , F ( 0 ) = 1 其中k>=1,F(0)=1 其中k>=1F(0)=1

看出什么了吗?

其实这道题就是Catalan number!!

只不过n的初值少 1 1 1。。。

2.2 组合数

首先,每一种进出栈的顺序都与出栈序列一一对应

也就是说,如果我们用 + 1 +1 +1表示进栈, − 1 -1 1表示出栈

那么举个例子:

出栈序列 1 , 3 , 4 , 2 1,3,4,2 1,3,4,2 与进出栈顺序 + 1 , + 1 , − 1 , + 1 , + 1 , − 1 , − 1 , − 1 +1,+1,-1,+1,+1,-1,-1,-1 +1,+1,1,+1,+1,1,1,1 是对应的

那么对 n n n个数的序列,总的进出栈顺序不是就是给 2 n 2n 2n 1 1 1前面挑 n n n个添加 + + +

其他的添加 − - 号,共 C 2 n n {\rm C}_{2n}^n C2nn种吗?

答案是否定的,这是因为出栈的前提是有进栈

于是要求每个排列中的前若干项和均不为负数

即排列 1 , − 1 , − 1 , 1 , 1 , − 1 , − 1 , 1 1,-1,-1,1,1,-1,-1,1 1,1,1,1,1,1,1,1是无效的

那么无效的排列到底有多少呢?

考虑 M M M是所有无效的排列构成的集合

考虑其中第一次发现排列无效的时候,也就是第一次发现其前若干项和为 − 1 -1 1的时候

此时我们将包含使得前若干项和为 − 1 -1 1的这一项开始的之前的所有项全都取相反数

那么就会得到一个新的排列

这个排列包含 n + 1 n+1 n+1 + 1 +1 +1,以及 n − 1 n-1 n1 − 1 -1 1

设所有这样的排列构成集合 N N N

显然,这个 M → N M\to N MN的映射是一一映射的

N N N中的每一个排列从第一项往后累积求和的时候必然会出现和为 + 1 +1 +1的情形,

此时将排列中使得和为 + 1 +1 +1的这一项连同之前的所有项全部取相反数,

那么就会得到 M M M中的一个排列)

因此无效的排列共有 C 2 n n − 1 {\rm C}_{2n}^{n-1} C2nn1个.

综上,所有不同的出栈序列总数为 C 2 n n − C 2 n n − 1 {\rm C}_{2n}^n-{\rm C}_{2n}^{n-1} C2nnC2nn1,即 C 2 n n n + 1 \dfrac {{\rm C}_{2n}^n}{n+1} n+1C2nn

也就是卡特兰数!

3 代码实现(注释)

3.1定义

//Cn为卡特兰数的第n项
int n;//输入
int ans[N];//高精度答案数组
int len=1//答案的位数
int p[N];//质数
int c[N];//c[i]是Cn中p的个数
int tot;//2*n中质数的个数
bool vis[N];//埃氏筛标记数组

3.2 输入

就输入一个n

scanf("%d",&n);//输出的

3.3 预处理

预处理质数,这里我用的是埃氏筛(用欧拉筛也行)

 for(int i=2;i<=n*2;i++) {//预处理1-2n之间的质数
   	if(!vis[i]) {//vis数组没有被标记过
		p[++tot]=i;//统计质数
	     for(int j=i;j<=N;j+=i) vis[j]=1;//大量优化复杂度!
   	}//就是埃氏筛
} 

3.4 高精度

这个乘法还是挺显然的

void jing(int x) {//高精乘法 乘x
    for(int i=1;i<=len;i++) ans[i]*=x;//直接乘x
    len+=6;//长度最多+6
    for(int i=1;i<=len;i++) {//每一位处理
        ans[i+1]+=ans[i]/10;
        ans[i]%=10;
    }//进位
    while(!ans[len]) len--;//将多加的长度剪掉
}

3.5 计算

先考虑(n)小一点的做法,我们可以直接用卡特兰数的线性递推公式:

f n = f n − 1 4 n − 2 n + 1 f_n=f_{n-1}\frac{4n-2}{n+1} fn=fn1n+14n2

然后看一眼数据,显然不行!

然后考虑一下优化

我们要换一个思路,应该用这个公式:

C n = C 2 n n n + 1 C_n=\frac{C_{2n}^n}{n+1} Cn=n+1C2nn

大力感谢zjy提供的思路!!!

3.5.1 转换公式

首先,由卡特兰数的通项公式 C n = C 2 n n n − 1 C_n= \frac{C_{2n}^n}{n-1} Cn=n1C2nn

⇒ C n = ! ( 2 n ) / ( ! n ) 2 n + 1   ⇒ C n = ! ( 2 n ) ( ! n ) 2 ( n + 1 ) ⇒C_n=\frac{!(2n)/(!n)^2}{n+1} \\ \ \\⇒C_n=\frac{!(2n)}{(!n)^2(n+1)} Cn=n+1!(2n)/(!n)2 Cn=(!n)2(n+1)!(2n)

最终表示为阶乘形式: C n = ( 2 n ) ! n ! ( n − 1 ) ! C_n= \frac{(2n)!}{n!(n-1)!} Cn=n!(n1)!(2n)!

再看一下算数基本定理:

A = p 1 a 1 ∗ p 2 a 2 ∗ . . . ∗ p k a k A=p_1^{a_1}*p_2^{a_2}*...*p_k^{a_k} A=p1a1p2a2...pkak

注意到卡特兰数每一项都一定是一个整数

也就是说,如果将如上公式的分子分母分解质因数

那么分母的各个因式指数上一定是可以被分子的各个因式相减抵消的

但是分子分母都是存在阶乘结构的,那么就考虑然后对某个数的阶乘分解质因数

3.5.2 分解阶乘

其次,一个质数 p [ i ] p[i] p[i] n ! n! n中可分解的个数为 n / p [ i ] n/p[i] n/p[i]

口胡一下证明:

n ! = 1 × 2 × 3 × . . . × ( n − 1 ) × n n!=1\times 2\times 3\times... \times(n-1)\times n n!=1×2×3×...×(n1)×n

所以在 n ! n! n!中(即 1 − n 1-n 1n中)是 p [ i ] p[i] p[i]的倍数的个数显然为 n / p [ i ] n/p[i] n/p[i]

然后,在 n ! n! n!中可被至少包含 k k k m m m次方的个数 x [ m ] x[m] x[m]为:

x [ m ] = ( 2 n ) / p [ i ] m − n / p [ i ] m − ( n − 1 ) / p [ i ] m x[m]=(2n)/p[i]^m-n/p[i]^m-(n-1)/p[i]^m x[m]=(2n)/p[i]mn/p[i]m(n1)/p[i]m

最终, C n C_n Cn中包含 p [ i ] p[i] p[i]的个数为

所有 n n n能被 p [ i ] m p[i]^m p[i]m整除的 m m m x [ m ] x[m] x[m]之和

所以 c [ i ] = ∑ j = 1 m x [ j ] c[i]=\sum^m_{j=1}x[j] c[i]=j=1mx[j]

口胡证明如下:


证明1:

不妨设 k 3 ∣ n ! k^3|n! k3n!且最多只能被 k k k的3次方整除

m = 1 m=1 m=1时, x [ 1 ] = ( 2 n ) / k − n / k − ( n − 1 ) / k x[1]=(2n)/k-n/k-(n-1)/k x[1]=(2n)/kn/k(n1)/k

m = 2 m=2 m=2时, x [ 2 ] = ( 2 n ) / k 2 − n / k 2 − ( n − 1 ) / k 2 x[2]=(2n)/k^2-n/k^2-(n-1)/k^2 x[2]=(2n)/k2n/k2(n1)/k2

m = 2 m=2 m=2时, x [ 3 ] = ( 2 n ) / k 3 − n / k 3 − ( n − 1 ) / k 2 x[3]=(2n)/k^3-n/k^3-(n-1)/k^2 x[3]=(2n)/k3n/k3(n1)/k2

由于 x x x数组的定义是 x [ m ] x[m] x[m] n ! n! n!(即 1 − n 1-n 1n)中至少包含 k k k m m m次方的数的个数

所以, n ! n! n!中只包含k的1次方的数的个数为 x [ 1 ] − x [ 2 ] x[1]-x[2] x[1]x[2]

同理可得, n ! n! n!中只包含k的1次方的数的个数为 x [ 2 ] − x [ 3 ] x[2]-x[3] x[2]x[3]

又因为 n ! n! n!且最多只能被 k k k的3次方整除,所以 n ! n! n!中只包含k的1次方的数的个数为 x [ 3 ] x[3] x[3]

所以总的包含的 k k k的个数 c c c

c = ( x [ 1 ] − x [ 2 ] ) × 1 + ( x [ 2 ] − x [ 3 ] ) × 2 + x [ 3 ] × 3 c=(x[1]-x[2])\times1+(x[2]-x[3])\times2+x[3]\times3 c=(x[1]x[2])×1+(x[2]x[3])×2+x[3]×3

化简后得: c = x [ 1 ] + x [ 2 ] + x [ 3 ] c=x[1]+x[2]+x[3] c=x[1]+x[2]+x[3]

根据数学归纳法,

c [ i ] = ∑ j = 1 m x [ j ] c[i]=\sum^m_{j=1} x[j] c[i]=j=1mx[j]

证毕


证明2:

对于 n ! = 1 × 2 × . . . × n n!=1\times2\times...\times n n!=1×2×...×n,考虑它存在多少个质因子 p p p

  • 显然有 ⌊ n p ⌋ \lfloor\frac{n}{p}\rfloor pn个数有至少 1 1 1个因子 p p p
  • 那么,有 ⌊ n p 2 ⌋ \lfloor\frac{n}{p^2}\rfloor p2n个数有至少 2 2 2个因子 p p p
  • . . . ... ...
  • 依次类推,有 ⌊ n p x ⌋ \lfloor\frac{n}{p^x}\rfloor pxn个数有至少 x x x个因子 p p p

循环枚举每一个质因子,对于每一个质因子

所以我们通过枚举 1 − 2 n 1-2n 12n的质数来进行拆分

再将拆分出的 p [ i ] c [ i ] p[i]^{c[i]} p[i]c[i]乘进 a n s ans ans

就ok了

可得代码:

 for(int i=1;i<=tot;i++) {//枚举质数
 	int now=p[i];
    	while(now<=n*2) {//c[i]指出现的个数
            c[i]+=n*2/now-n/now-(n+1)/now;
            now*=p[i];
        }
    while(c[i]--) jing(p[i]);//乘p[i]的c[i]次方
}

3.6 输出

输出高精ans数组即可

for(int i=len;i>=1;i--) {
	printf("%d",ans[i]);
}//注意要倒着输出

3.7 总体代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1200000;
int n,ans[N],len=1,p[N],c[N],tot;
bool vis[N];
void jing(int x) {
    for(int i=1;i<=len;i++) ans[i]*=x;
    len+=6;//最多加六位(*的数小于等于n*2)
    for(int i=1;i<=len;i++) {
        ans[i+1]+=ans[i]/10;
        ans[i]%=10;
    }
    while(!ans[len]) len--;
}//高精度乘法
int main(){
	scanf("%d",&n);//初始化
    ans[1]=1;//初始化高精ans=1
    for(int i=2;i<=n*2;i++) {
    	if(!vis[i]) {
	       	 p[++tot]=i;//统计质数
	       	 for(int j=i;j<=N;j+=i) vis[j]=1;//排除合数
            //原因显然,质数的k(k≠1)倍肯定不是质数,证明如下:
            //质数的定义是:一个数只有1和它本身两个因数,称这个数叫做质数
            //质数(p[i])的k(k≠1)倍已经有了k和p[i]两个(不是1和它本身)的因数
            //综上所述,质数的k(k≠1)倍肯定不是质数
   		}//埃氏筛求2*n以内的质数
    } //预处理
    for(int i=1;i<=tot;i++) {//枚举质数
        int now=p[i];
        while(now<=n*2) {//c[i]指出现的个数
            c[i]+=n*2/now-n/now-(n+1)/now;
            now*=p[i];
        }
        while(c[i]--) jing(p[i]);//高精乘法
    }//计算
    for(int i=len;i>=1;i--) {
    	printf("%d",ans[i]);
    }//输出
	return 0;
}

4 总结

​ 就是卡特兰数高精的一种奇妙并且码量少的一种写法!

完结撒花❀
★,°:.☆( ̄▽ ̄)/$:.°★

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/1050.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于Anaconda的下载和安装方法及报错说明

初学者接触python时&#xff0c;常会因各种环境问题、各种包的安装问题而苦恼&#xff0c;Anaconda则可以解决这一切繁琐的问题&#xff0c;但很多人不知道如何下载安装配置&#xff0c;本文详细讲述下载和安装配置过程&#xff0c;也汇总常见安装过程中的错误&#xff08;零基…

【3】核心易中期刊推荐——人工智能计算机仿真

🚀🚀🚀NEW!!!核心易中期刊推荐栏目来啦 ~ 📚🍀 核心期刊在国内的应用范围非常广,核心期刊发表论文是国内很多作者晋升的硬性要求,并且在国内属于顶尖论文发表,具有很高的学术价值。在中文核心目录体系中,权威代表有CSSCI、CSCD和北大核心。其中,中文期刊的数…

【Kubernetes】第二十八篇 - 实现自动构建部署

一&#xff0c;前言 上一篇&#xff0c;介绍了 Deployment、Service 的创建&#xff0c;完成了前端项目的构建部署&#xff1b; 希望实现&#xff1a;推送代码 -> 自动构建部署-> k8s 滚动更新&#xff1b; 本篇&#xff0c;实现自动构建部署 二&#xff0c;推送触发构…

28个案例问题分析---15---登陆之后我加入的课程调用接口报错--ArrayList线程不安全。占用内存情况

ArrayList线程不安全。占用内存情况故事背景方案&思路解决线程不安全的问题方案一&#xff1a;在这两个方法之前添加 synchronized 关键字。方案二&#xff1a;使用ThreadLocal变量。解决重复创建对象问题。总结&升华故事背景 存入redis的值&#xff0c;可能会出现错误…

黑马《数据结构与算法2023版》正式发布

有人的地方就有江湖。 在“程序开发”的江湖之中&#xff0c;各种技术流派风起云涌&#xff0c;变幻莫测&#xff0c;每一位IT侠客&#xff0c;对“技术秘籍”的追求和探索也从未停止过。 要论开发技术哪家强&#xff0c;可谓众说纷纭。但长久以来&#xff0c;确有一技&#…

实用调试技巧【详细介绍】

实用调试技巧1. 什么是bug&#xff1f;2. 调试是什么&#xff1f;有多重要&#xff1f;2.1 调试是什么&#xff1f;2.2 调试的基本步骤2.3 Debug和Release的介绍3. Windows环境调试介绍3.1 调试环境的准备3.2 学会快捷键3.3 调试的时候查看程序当前信息3.3.1 查看临时变量的值3…

Java中的异常

程序错误一般分为三种&#xff1a;编译错误&#xff1a; 编写程序时没有遵循语法规则&#xff0c;编译程序能够自己发现错误并提示位置和原因。运行错误&#xff1a;程序在执行的时候运行环境发现了不能执行的操作。比如&#xff0c;JVM出错了&#xff0c;内存溢出等。逻辑错误…

Docker常用项目实战演练

docker镜像源的修改 linux环境下编辑 /etc/docker/daemon.json vi /etc/docker/daemon.json #如添加如下网易镜像源 { "registry-mirrors": ["http://hub-mirror.c.163.com"] }docker run命令详细解释 日常工作中用的比较多的是docker run命令&#xff…

2023年目标检测毕业设计(yolov5车辆识别、车辆检测、车牌识别、行人识别)

车辆识别视频yolov5车辆识别视频yolov5 yoloR对比行人车辆识别视频yolov8识别视频订阅专栏获得源码:http://t.csdn.cn/zsG47 ​​​​​​​先看一下yolo发展史 二、单目测距原理 图中有一个车辆&#xff0c;且车辆在地面上&#xff0c;其接地点Q必定在地面上。那么Q点的深度便…

少儿Python每日一题(23):楼梯问题

原题解答 本次的题目如下所示&#xff1a; 楼梯有n阶台阶&#xff0c;上楼可以一步上1阶&#xff0c;也可以一步上2阶&#xff0c;走完n阶台阶共有多少种不同的走法&#xff1f; 输入格式&#xff1a; 输入楼梯的阶梯数n 输出格式&#xff1a; 输出不同走法的个数 输入样例&am…

Unity学习日记12(导航走路相关、动作完成度返回参数)

目录 动作的曲线与函数 创建遮罩 导航走路 设置导航网格权重 动作的曲线与函数 执行动作&#xff0c;根据动作完成度返回参数。 函数&#xff0c;在代码内执行同名函数即可调用。在执行关键帧时调用。 创建遮罩 绿色为可效用位置 将其运用到Animator上的遮罩&#xff0c;可…

嵌入式学习笔记——STM32寄存器编程实现外部中断

外部中断前言EXTI的介绍EXTI是什么EXTI的主要特性数量对应中断源的命名EXTI的框图配置流程寄存器介绍编程思路编程效果前言 上一篇中&#xff0c;介绍了关于STM32的中断管理以及具体配置&#xff0c;本文就使用之前的配置流程来实现一下外部中断的功能。 EXTI的介绍 EXTI是什…

SDIO读写SD卡速度有多快?

前两天测试了SPI方式读写SD卡的速度《SPI方式读写SD卡速度测试》&#xff0c;今天来测试一下SDIO方式的读写速度。测试条件&#xff1a;单片机&#xff1a;STM32F407VET6编译环境&#xff1a;MDK 5.30HAL库SD卡&#xff1a;闪迪32GB/64GB TF卡文件系统&#xff1a;FatFS R0.12c…

SpringCloud详解01-SpringCloudAlibaba、Nacos

文章目录前言一、架构演进1、web1.0阶段2、web2.0阶段3、垂直架构4、分布式架构二、SpringCloud基本概念1.特点2.SpringCloud和SpringCloudAlibaba3.SpringCloudAlibaba体系核心组件三、SpringCloudAlibaba1、注册中心Nacos2、Nacos安装和启动总结前言 本篇记录一下SpringClou…

ChatGPT研究分享:机器第一次开始理解人类世界

0、为什么会对ChatGPT感兴趣一开始&#xff0c;我对ChatGPT是没什么关注的&#xff0c;无非就是有更大的数据集&#xff0c;完成了更大规模的计算&#xff0c;所以能够回答更多的问题。但后来了解到几个案例&#xff0c;开始觉得这个事情并不简单。我先分别列举出来&#xff0c…

每日学术速递3.17

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Breaking Common Sense: WHOOPS! A Vision-and-Language Benchmark of Synthetic and Compositional Images 标题&#xff1a;打破常识&#xff1a;哎呀&#xff01;合成和合成图像…

【Redis】搭建哨兵集群

目录 集群结构 准备实例和配置 启动 测试 集群结构 这里我们搭建一个三节点形成的Sentinel集群&#xff0c;来监管之前的Redis主从集群。如图&#xff1a; 三个sentinel实例信息如下&#xff1a; 节点IPPORTs1192.168.150.10127001s2192.168.150.10127002s3192.168.150.…

python并发编程多线程

在传统操作系统中&#xff0c;每个进程有一个地址空间&#xff0c;而且默认就有一个控制线程 线程顾名思义&#xff0c;就是一条流水线工作的过程&#xff0c;一条流水线必须属于一个车间&#xff0c;一个车间的工作过程是一个进程 车间负责把资源整合到一起&#xff0c;是一个…

C语言指针操作(十)动态内存分配与指向它的指针变量

目录 一、什么是内存的动态分配 二、怎样建立内存的动态分配 2.1用malloc函数开辟动态存储区 2.2用calloc函数开辟动态存储区 2.3用realloc函数重新分配动态存储区 2.4用free函数释放动态存储区 三、void指针类型 四、举例说明 一、什么是内存的动态分配 全局变量是分…

redis持久化的几种方式

一、简介 Redis是一种高级key-value数据库。它跟memcached类似&#xff0c;不过数据可以持久化&#xff0c;而且支持的数据类型很丰富。有字符串&#xff0c;链表&#xff0c;集 合和有序集合。支持在服务器端计算集合的并&#xff0c;交和补集(difference)等&#xff0c;还支持…
最新文章