Q:首先什么是卡特兰数?
A:就是一个数列,其中的数满足一些规律(如下)
令h(0)=1,h(1)=1,catalan数满足递推式:
h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (n>=2)
例如:h(2)=h(0)*h(1)+h(1)*h(0)=1*1+1*1=2
h(3)=h(0)*h(2)+h(1)*h(1)+h(2)*h(0)=1*2+1*1+2*1=5
另类递推式:
h(n)=h(n-1)*(4*n-2)/(n+1);
递推关系的解为:
h(n)=C(n,2n)*(2n-1) (n=0,1,2,...)
递推关系的另类解为:
h(n)=c(2n,n)-c(2n,n+1)(n=0,1,2,...)
有关卡特兰数的题的关键在于大数据的处理,灵活选取公式,方便取模运算。
Q:都有哪些类型的题?
A:
1、括号化问题。矩阵链乘: P=A1×A2×A3×……×An,依据乘法结合律,不改变其顺序,
只用括号表示成对的乘积,试问有几种括号化的方案?
(不是很懂这个题意,emmm,懂了以后翻出来再讲)
解决方案:求n的卡特兰数
2、将多边行划分为三角形问题。将一个凸多边形区域分成三角形区域(划分线不交叉)的
方法数?
类似:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?
解决方案:求2n的卡特兰数
3、出栈次序问题。一个栈(无穷大)的进栈序列为1、2、3、...、n,有多少个不同的出栈序列?
类似:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只
有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的
钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)
类似:一位大城市的律师在他住所以北n个街区和以东n个街区处工作,每天走2n个街区去
上班。如果他从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?
(将向某个方向走定义为入栈,另一个方向即为出栈)
类似:给出代码的那道例题(奇数项定义为入栈,偶数项定义为出栈)
解决方案:求2n或n(看题意而定,对数还是个数)的卡特兰数
4、给顶节点组成二叉树的问题。
给定n个节点,能构成多少种形状不同的二叉树?(题目不一定直接告诉你这就是二叉树)
解决方案:求n的卡特兰数
例题:
E - 有趣的数列 HYSBZ - 1485 (暑训D1E)
有趣的数列,这道题若是用公式h(n)=h(n-1)*(4*n-2)/(n+1),考虑到取模的数不一定是素数,不能直接去找逆元。
所以用公式h(n)=C(n,2n)*(2n-1) (n=0,1,2,...)解决。(细节在代码中已有解释)
#include<stdio.h>
#include<string.h>
#define mem(x,num) memset(x,num,sizeof(x))
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
using namespace std;
const ll maxn=1e6+10;
ll n,mod,ans=1,cnt=0;
ll prime[maxn],mn[maxn<<1],num[maxn<<1];
bool not_prime[maxn<<1];//1 不是素数;0 是素数
void get_prime()
{
rep(i,2,2*n)
{
if(!not_prime[i])prime[++cnt]=i,mn[i]=cnt;
//prime数组表示第i个素数是多少,mn数组表示到i(包括i)有几个素数
for(int j=1;prime[j]*i<=2*n;j++)
{
not_prime[prime[j]*i]=1;mn[prime[j]*i]=j;
if(i%prime[j]==0) break;
}
}
}
void add(int x,int v)
{
while(x!=1)
{
num[mn[x]]+=v;
x/=prime[mn[x]];
}
}//将c(2n,n)质因数分解,记录下每个质因数的个数,最后相乘取模即可
int main()
{
scanf("%lld%lld",&n,&mod);
get_prime();
dep(i,2*n,n+1)add(i,1);//上面的乘
rep(i,1,n)add(i,-1);//下面的除
add(n+1,-1);//公式:h(n)=c(2n,n)/(n+1)
rep(i,1,cnt)
while(num[i]--)ans=(ans*prime[i])%mod;
printf("%lld\n",ans);
return 0;
}
补充:若是题目要求直接输出,不要取模,则需要用到高精度乘法模板,和快速幂
#include<stdio.h>
#define maxn 120005
#define ll long long
using namespace std;
int n,g;
int a,b,fm[maxn],fz[maxn],ans[maxn*100];
int qpow(int a,int b)//快速幂
{
int res=1;
for(;b;b>>=1)
{
if(b&1) res=res*a;
a*=a;
}
return res;
}
//ans[0]存大整数的位数,ans数组倒序储存大数
void mul(int x)
{
int k=0;//向前一步的进位
for(int i=1;i<=ans[0];i++)
{
ans[i]*=x;
ans[i]+=k;
k=ans[i]/10;
ans[i]%=10;
}
while(k)
{
ans[++ans[0]]+=k;//位数增加
k=ans[ans[0]]/10;
ans[ans[0]]%=10;
}
}
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++)
{
a=n+i;//分子
for(int j=2;j*j<=a;j++)//质因数分解(只到1000,不用素数打表)
{
while(a%j==0)fz[j]++,a/=j;
}
if(a>1) fz[a]++;
b=i;//分母
for(int j=2;j*j<=b;j++)
{
while(b%j==0)fm[j]++,b/=j;
}
if(b>1) fm[b]++;
}
ans[0]=ans[1]=1;
for(int i=2;i<=n*2;i++)
{
if(fz[i]==0) continue;
fz[i]=fz[i]-fm[i];
if(fz[i]==0) continue;
int x=qpow(i,fz[i]);//感觉int可能会爆,不行改long long,或者直接每个因子直接用mul函数过
if(x!=1) mul(x);
}
for(int i=ans[0];i>=1;--i)
{
printf("%d",ans[i]);
}
return 0;
}