数论(1)——卡特兰数

版权声明:转载请联系作者 https://blog.csdn.net/xiaoshikjj/article/details/82079810

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;
}

猜你喜欢

转载自blog.csdn.net/xiaoshikjj/article/details/82079810
今日推荐