『火车进出栈问题 卡特兰数』
<更新提示>
<第一次更新>
<正文>
火车进出栈问题
Description
一列火车n节车厢,依次编号为1,2,3,…,n。每节车厢有两种运动方式,进栈与出栈,问n节车厢出栈的可能排列方式有多少种。
Input Format
一个数,n(n<=65000)
Output Format
一个数s表示n节车厢出栈的可能排列方式
Sample Input
3
Sample Output
5
解析
转换一下题意:求卡特兰数的第\(n\)项,不取模。
先考虑\(n\)小一点的做法,我们可以直接用卡特兰数的线性递推公式:
然后直接敲高精度乘除就可以得到\(52\)分的高分啦。
然后考虑一下优化:我们发现数字位数超级多,可以写一个压位高精,这样又可以多得\(20-30\)分。
然后再想一想正解,我们要换一个思路,应该用这个公式:
这是卡特兰数的通项公式,我们可以利用组合数的定义式简单的推导一下。
看一下算数基本定理:
注意到卡特兰数每一项都一定是一个整数,也就是说,如果将如上公式的分子分母分别分解质因数,那么分母的各个因式指数上一定是可以被分子的各个因式相减抵消的。
这样,高精度除法就不用写了,这是一个巨大的突破。
但是分子分母都是存在阶乘结构的,那么就考虑然后对某个数的阶乘分解质因数。
对于\(!n=1*2*...*n\),考虑他存在多少个质因子\(p\)。
- 显然有\(\lfloor\frac{n}{p}\rfloor\)个数有至少一个因子\(p\)
- 那么,有\(\lfloor\frac{n}{p^2}\rfloor\)个数有至少两个因子\(p\)
- \(...\)
- 依次类推,有\(\lfloor\frac{n}{p^x}\rfloor\)个数有至少\(x\)个因子\(p\)
代码片段:将\(!n\)分解质因数
\(Code:\)
//prime[i]存了1-n范围内的所有质数
for(int i=1;i<=cnt;i++)
{
if(prime[i]>n)break;
int sum=0;
for(int j=prime[i];j<=n;j*=prime[i])
sum+=n/j;
p[++tot]=prime[i];a[tot]=sum;
}
那么我们就能分解质因数了。先线性筛法预处理\(2n\)范围内的所有质数,然后循环枚举每一个质因子,对于每一个质因子,我们借助如上规律累乘质因子\(p\),再累加每一个\(\lfloor\frac{2n}{p^x}\rfloor\)即可得到因子\(p\)在\(!(2n)\)的分解式中的指数大小,从而得到\(!(2n)\)的分解式。
同理,我们可以继续用这种方法分解\((!n)^2\),再将相同因子的质数减去即可。
对于\(n+1\),我们直接暴力分解一下,将相同因子的质数减去,就得到了最终答案的分解式,然后快速幂配合高精度乘法做一下就可以了。
\(Code:\)
#include<bits/stdc++.h>
using namespace std;
const int base=1e8,Maxlen=1e4;
struct bign
{
int d[Maxlen],len;
inline void clear(void)
{
len=0;
memset(d,0,sizeof d);
}
inline void print(void)
{
printf("%d",d[len]);
for(int i=len-1;i>=1;i--)
printf("%08d",d[i]);
}
bign operator = (int a)
{
clear();
do
{
d[++len]=a%base;
a/=base;
}
while(a);
return *this;
}
bign operator * (bign a)
{
bign res;
res.clear();
long long temp;
for(int i=1;i<=len;i++)
{
temp=0;
for(int j=1;j<=a.len;j++)
{
temp+=1LL*d[i]*a.d[j]+res.d[i+j-1];
res.d[i+j-1]=temp%base;
temp/=base;
}
if(temp)
res.d[i+a.len]=temp;
}
res.len=len+a.len;
while(!res.d[res.len]&&res.len>1)res.len--;
return res;
}
};//结构体封装的大整数高精度
const int N=65000+20;
int n,vis[N*2],prime[N*2],cnt,p[N*2],a[N*2],reto[N*2],tot;
inline void Sieve(void)
{
//线性筛法
for(int i=2;i<=2*n;i++)
{
if(!vis[i])prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<=n*2;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
inline long long power(long long a,long long b)
{
//快速幂
long long res=1;
while(b)
{
if(1&b)res*=a;
a*=a;b>>=1;
}
return res;
}
inline void solve(void)
{
//先分解!(2n)
for(int i=1;i<=cnt;i++)
{
if(prime[i]>2*n)break;
int sum=0;
for(int j=prime[i];j<=2*n;j*=prime[i])
sum+=(2*n)/j;
p[++tot]=prime[i];a[tot]=sum;
//p存底数,a存指数,tot为下标
reto[prime[i]]=tot;
//记录一下,对于质因数prime[i],它在分解式中对应的下标为reto[prime[i]]
}
//分解!n
for(int i=1;i<=cnt;i++)
{
if(prime[i]>n)break;
int sum=0;
for(int j=prime[i];j<=n;j*=prime[i])
sum+=n/j;
//!n有两个,将指数一起减去
a[reto[prime[i]]]-=(2*sum);
}
//暴力分解一下n+1
int m=n+1;
for(int i=1;i<=cnt;i++)
{
if(prime[i]>m)break;
if(m%prime[i]==0)
{
int sum=0;
while(m%prime[i]==0)
{
sum++;
m/=prime[i];
}
//将指数减去
a[reto[prime[i]]]-=sum;
}
}
bign ans,temp;
ans=1;
for(int i=1;i<=tot;i++)
{
//将分解式乘起来就是答案
temp=power(p[i],a[i]);
ans=ans*temp;
}
ans.print();
}
int main(void)
{
freopen("easy.in","r",stdin);
freopen("easy.out","w",stdout);
scanf("%d",&n);
Sieve();
solve();
return 0;
}
<后记>