题目描述
一列火车n节车厢,依次编号为$1,2,3,…,n$。
每节车厢有两种运动方式,进栈与出栈,问n节车厢出栈的可能排列方式有多少种。
输入格式
输入一个整数n,代表火车的车厢数。
输出格式
输出一个整数s表示n节车厢出栈的可能排列方式数量。
数据范围
$1 \le n \le 60000$
输入样例:
3
输出样例:
5
解题思路
题意分析
首先这道题目,就是上一道火车进出问题的统计方案数版本.
题目意思分析后:就是让我们求出按照题目所说的顺序进入栈,一共有多少种入栈顺序.
算法解析
首先看到$n=60000$,我们肯定要脱口而出WOC这道题目居然要用高精度,然后Python同学们丝毫不慌.
首先我们要简化或者说是,转化题目意思,我们可以将一个入栈顺序,改成+-号顺序问题.
+-号顺序问题,也就是对于一个入栈顺序,将其入栈操作, 视为+号,出栈操作,视为-号
- 对于加号而言,我们相当于当前这个火车入栈.
- 对于减号而言,我们相当于当前栈的栈顶火车出栈.
然后我们接着这道题目就转化为了统计+-号顺序问题.而最后的答案,我们称之为卡特兰数.
什么是卡特兰数
出栈次序
一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列? [5-6]
常规分析
首先,我们设f(n)=序列个数为n的出栈序列种数。(我们假定,最后出栈的元素为k,显然,k取不同值时的情况是相互独立的,也就是求出每种k最后出栈的情况数后可用加法原则,由于k最后出栈,因此,在k入栈之前,比k小的值均出栈,此处情况有f(k-1)种,而之后比k大的值入栈,且都在k之前出栈,因此有f(n-k)种方式,由于比k小和比k大的值入栈出栈情况是相互独立的,此处可用乘法原则,f(n-k)*f(k-1)种,求和便是Catalan递归式。ps.author.陶百百)
首次出空之前第一个出栈的序数k将1~n的序列分成两个序列,其中一个是1~k-1,序列个数为k-1,另外一个是k+1~n,序列个数是n-k。
此时,我们若把k视为确定一个序数,那么根据乘法原理,f(n)的问题就等价于——序列个数为k-1的出栈序列种数乘以序列个数为n - k的出栈序列种数,即选择k这个序数的f(n)=f(k-1)×f(n-k)。而k可以选1到n,所以再根据加法原理,将k取不同值的序列种数相加,得到的总序列种数为:f(n)=f(0)f(n-1)+f(1)f(n-2)+……+f(n-1)f(0)。
看到此处,再看看卡特兰数的递推式,答案不言而喻,即为f(n)=h(n)= C(2n,n)/(n+1)= c(2n,n)-c(2n,n-1)(n=0,1,2,……)。
最后,令f(0)=1,f(1)=1。
非常规分析
对于每一个数来说,必须进栈一次、出栈一次。我们把进栈设为状态‘1’,出栈设为状态‘0’。n个数的所有状态对应n个1和n个0组成的2n位二进制数。由于等待入栈的操作数按照1‥n的顺序排列、入栈的操作数b大于等于出栈的操作数a(a≤b),因此输出序列的总数目=由左而右扫描由n个1和n个0组成的2n位二进制数,1的累计数不小于0的累计数的方案种数。
在2n位二进制数中填入n个1的方案数为c(2n,n),不填1的其余n位自动填0。从中减去不符合要求(由左而右扫描,0的累计数大于1的累计数)的方案数即为所求。
不符合要求的数的特征是由左而右扫描时,必然在某一奇数位2m+1位上首先出现m+1个0的累计数和m个1的累计数,此后的2(n-m)-1位上有n-m个 1和n-m-1个0。如若把后面这2(n-m)-1位上的0和1互换,使之成为n-m个0和n-m-1个1,结果得1个由n+1个0和n-1个1组成的2n位数,即一个不合要求的数对应于一个由n+1个0和n-1个1组成的排列。
反过来,任何一个由n+1个0和n-1个1组成的2n位二进制数,由于0的个数多2个,2n为偶数,故必在某一个奇数位上出现0的累计数超过1的累计数。同样在后面部分0和1互换,使之成为由n个0和n个1组成的2n位数,即n+1个0和n-1个1组成的2n位数必对应一个不符合要求的数。
因而不合要求的2n位数与n+1个0,n-1个1组成的排列一一对应。
显然,不符合要求的方案数为c(2n,n+1)。由此得出输出序列的总数目=c(2n,n)-c(2n,n-1)=c(2n,n)/(n+1)=h(n)。
以上引用百度百科,原址
代码实现
#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
const ll M=1e9;//M为压位的最大值
ll a[60004],l,sum[120004];
int n;
void Prime(int b,int f)
{
for(int j=2;j*j<=b && b!=1;j++)//质因数分解.
while(b%j==0)
{
sum[j]+=f;
b/=j;
}
if(b)
sum[b]+=f;
}
void High(ll c)
{
for(int i=1;i<=l;i++)
a[i]*=c;
for(int i=1;i<=l;i++)
a[i+1]+=a[i]/M,a[i]%=M;//我们需要压缩位置快速处理
while(a[l+1])
++l;
}
int main()
{
a[1]=1,l=1;
scanf("%d",&n);
for(int i=1;i<=n;i++)//对于两个组合数相除,我们这道题目必须使用快速的质因数分解法,去处理.
Prime(n+i,1);
for(int i=2;i<=n+1;i++)
Prime(i,-1);
for(int i=2;i<=2*n;i++)
for(ll j=0;j<sum[i];++j)
High(i);//高精度
printf("%lld",a[l]);
for(ll i=l-1;i;--i)
printf("%09lld",a[i]);//输出
return 0;
}
服了。。。。
=v=我已经打算学另外一种语言了
还是要压位吗。。。
可以有不用压位的写法吗
为什么会想到分解质因数再乘
因为这是卡特兰数,而且质因数分解才能快速处理本题,降低时间复杂度.