火车进出栈问题 题解
创始人
2025-05-30 01:16:06
0

来源 卡特兰数

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

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

1 题面

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

输入

一个数,n(n<=60000)

输出

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

样例

样例输入1

3

样例输出1

5

2 分析题面

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

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

2.1 递推

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

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

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

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

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

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

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

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

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

所以,F(0)=1F(0)=1F(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(k−1)+F(1)×F(k−2)+F(2)×F(k−3)+...+F(k−2)×F(1)+F(k−1)×F(0)

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

看出什么了吗?

其实这道题就是Catalan number!!

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

2.2 组合数

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

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

那么举个例子:

出栈序列 1,3,4,21,3,4,21,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 是对应的

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

综上,所有不同的出栈序列总数为C2nn−C2nn−1{\rm C}_{2n}^n-{\rm C}_{2n}^{n-1}C2nn​−C2nn−1​,即C2nnn+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) {//高精乘法 乘xfor(int i=1;i<=len;i++) ans[i]*=x;//直接乘xlen+=6;//长度最多+6for(int i=1;i<=len;i++) {//每一位处理ans[i+1]+=ans[i]/10;ans[i]%=10;}//进位while(!ans[len]) len--;//将多加的长度剪掉
}

3.5 计算

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

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

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

然后考虑一下优化

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

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

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

3.5.1 转换公式

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

⇒Cn=!(2n)/(!n)2n+1⇒Cn=!(2n)(!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)​

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

再看一下算数基本定理:

A=p1a1∗p2a2∗...∗pkakA=p_1^{a_1}*p_2^{a_2}*...*p_k^{a_k}A=p1a1​​∗p2a2​​∗...∗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)×nn!=1\times 2\times 3\times... \times(n-1)\times nn!=1×2×3×...×(n−1)×n

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

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

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

最终,CnC_nCn​中包含p[i]p[i]p[i]的个数为

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

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

口胡证明如下:


证明1:

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

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

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

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

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

所以,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!且最多只能被kkk的3次方整除,所以n!n!n!中只包含k的1次方的数的个数为x[3]x[3]x[3]

所以总的包含的kkk的个数ccc为

c=(x[1]−x[2])×1+(x[2]−x[3])×2+x[3]×3c=(x[1]-x[2])\times1+(x[2]-x[3])\times2+x[3]\times3c=(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=1mx[j]c[i]=\sum^m_{j=1} x[j]c[i]=∑j=1m​x[j]

证毕


证明2:

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

  • 显然有⌊np⌋\lfloor\frac{n}{p}\rfloor⌊pn​⌋个数有至少111个因子ppp
  • 那么,有⌊np2⌋\lfloor\frac{n}{p^2}\rfloor⌊p2n​⌋个数有至少222个因子ppp
  • .........
  • 依次类推,有⌊npx⌋\lfloor\frac{n}{p^x}\rfloor⌊pxn​⌋个数有至少xxx个因子ppp

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

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

再将拆分出的p[i]c[i]p[i]^{c[i]}p[i]c[i]乘进ansansans中

就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
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=1for(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 总结

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

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

上一篇:尚融宝05-Node.js入门

下一篇:git统计命令

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...