卡特兰数

卡特兰数

又称明安图数。Catalan 是国外命名的,而最先发现的人应该是清代数学家明安图。

问题 1:

给定 \(n\)\(+1\)\(m\)\(-1\),求将其组成排列满足 \(\forall 1\leq i\leq n+m,\sum\limits_{1\leq j\leq i}a_j\ge -k\) 的方案数。


对于 \(n-m<-k\) 的情况,符合条件的排列数 \(0\),接下来考虑 \(n-m\ge -k\) 的情况:

排列总个数为 \({n+m\choose n}\),减去不符合条件的数量即可。

考虑排列 \(A\),它不符合条件,即 \(\exist 1\leq i< n+m,\sum\limits_{1\leq j\leq i}a_j<-k\),我们找到最小的 \(i\),显然它满足 \(\sum\limits_{1\leq j\leq i}a_j=-(k+1)\),也就是说,前 \(i\) 个数中 \(1\) 的个数比 \(-1\) 的个数少 \(k+1\) 个。

我们将排列 \(A\) 的前 \(i\) 项取相反数,得到序列 \(B\)。可以发现序列 \(B\) 满足前 \(i\) 个数中 \(1\) 的个数比 \(-1\) 的个数多 \(k+1\) 个,即:有 \(n+k+1\)\(1\),有 \(m-k-1\)\(-1\)

对于排列 \(A\),有唯一的序列 \(B\) 与之对应;对于序列 \(B\),有唯一的排列 \(A\) 与之对应。

证明可以考虑排列 \(A\) 第一个前缀和小于 \(-k\) 的位置,以及序列 \(B\) 第一个前缀和大于 \(k\) 的位置唯一,即在规定的变换方式下,两者有唯一的对应关系。

排列 \(A\) 的个数即为序列 \(B\) 的个数,那么序列 \(B\) 除了有 \(n+k+1\)\(1\),有 \(m-k-1\)\(-1\) 以外,还是否受到其他限制呢?即是否对于每一个不同的序列 \(B\),都有不同的排列 \(A\) 与之对应?这是显然的,上文已经证过,序列 \(B\) 必然满足 \(\exist 1\leq i\leq n+m,\sum\limits_{1\leq j\leq i}B_j>k\),所以每一个序列 \(B\) 都可以派上用场。

综上,序列 \(B\) 的个数为 \({n+m\choose n+k+1}\),则符合条件的方案数为:

\[{n+m\choose n}-{n+m\choose n+k+1} \]

问题 2:

\(n\) 个元素有多少种出栈顺序。


入栈看成 \(+1\),出栈看成 \(-1\),取 \(k=0\),即可计算出答案:

\[{2n\choose n}-{2n\choose n+1} \]

问题 3:

\(n\) 对括号能组成多少个有效的括号序列。


一个括号序列有效的条件是它的所有前缀中,左括号数不少于右括号数。

将左括号看成 \(+1\),右括号看成 \(-1\),取 \(k=0\),即可计算出答案:

\[{2n\choose n}-{2n\choose n+1} \]

问题 4:

\(n+1\) 个节点能构成多少个形状不同的满二叉树。

这里的满二叉树指的是对于任意一个节点,都满足为叶子节点或是有两个儿子。


可以发现,这样的满二叉树作为左儿子和作为右儿子的节点数目是一样的。

考虑一棵树的 dfs 序,将左儿子看成 \(+1\),右儿子看成 \(-1\),遍历的过程中累加而不小于 \(0\),记为一棵合法的满二叉树。

根节点不用考虑,略过。

所以答案还是:

\[{2n\choose n}-{2n\choose n+1} \]

问题 5:

上点难度。

CF896D Nephren Runs a Cinema

\(n\) 个人排队买电影票,分为三种类型:有一张 \(50\) 元的,有一张 \(100\) 元的,还有无需付钱的 VIP 客户。起初电影院没有零钱,求有多少种排队的方法,使得每个人都买到电影票后,工作人员手上的 \(50\) 元的张数在 \(l\sim r\) 之间,结果对 \(p\) 取模。只要两种方案里的其中一人的类别不同,就代表两种方案不同。


考虑先排除 VIP 客户的影响,枚举 VIP 客户的个数,对于 \(i\) 个 VIP 客户,贡献为 \({n \choose i}\)

枚举有 \(50\) 元人的个数 \(j\),则有 \(100\) 元人的个数为 \(n-i-j\),可以发现 \(j\) 满足:\(l\leq j-(n-i-j)\leq r\),意为 \(50\) 元最终的张数范围。

解得:\(\lceil\frac{l+n-i}{2}\rceil\leq j\leq \lfloor\frac{r+n-i}{2}\rfloor\),又由于对于每个 \(100\) 元的付款,都要找钱 \(50\),所以对于任意前缀都满足有 \(50\) 元的人的个数大于等于有 \(100\) 元的人的个数。

可得答案计算式:

\[\sum_{i=0}^n{n\choose i}\bigg(\sum_{j=\lceil\frac{l+n-i}{2}\rceil}^{\lfloor\frac{r+n-i}{2}\rfloor}{n-i\choose j}-{n-i\choose j+1}\bigg) \]

发现可以消掉其中大部分,化简为:

\[\sum_{i=0}^n{n\choose i}\bigg({n-i\choose \lceil\frac{l+n-i}{2}\rceil}-{n-i\choose \lfloor\frac{r+n-i}{2}\rfloor+1}\bigg) \]

做到这里,有关卡特兰数的部分就结束了,但是题目还没有解决。

题目给出的模数非质数,也不保证互质,不能使用费马小定理或扩展欧几里得求逆元。

这时候一种方法是使用 \(\rm{exLucas}\),另一种方法是从 \(p\) 入手:

发现 \(p\leq 2\times 10^9\) 最多有 \(9\) 个不同的质因子,我们将 \(p\) 分解质因数。

\[p=\prod_{i=1}^{num}p_i^{\alpha_i} \]

在预处理组合数的过程中,我们需要用到乘法和除法,不妨将 \(fac[i]=i!\) 表示成有关 \(p\) 的质因子的形式:

\[fac[i]=i!=p_0\times \prod_{i=1}^{num}p_i^{\beta_i} \]

可以发现,此时 \(\gcd(p_0,p)=1\),可以使用扩展欧几里得算法求逆元。

对于每一个阶乘,我们记录 \(p_0\) 以及 \(\{\beta_i\}\)。重载乘法和除法,关于乘法,我们将两个数的 \(\{\beta_i\}\) 相加,\(p_0\) 相乘并对 \(p\) 取模;关于除法,我们将两个数的 \(\{\beta_i\}\) 相减,\(p_0\) 求逆元相乘并对 \(p\) 取模,最后将答案还原即可。

注意开 long long,代码如下(个人感觉还是较为简洁明了的)。

#include<bits/stdc++.h>
using namespace std;

#define int long long

inline int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c<='9'&&c>='0'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}

void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10^48);
}

const int N=1e5+5;
int mod,num,p[10],a[10];
struct node{
	int p0=1,a0[10]={0};
}fac[N],invfac[N];

int calc(node x){
	int ans=x.p0;
	for(int i=1;i<=num;++i)while(x.a0[i]--)ans=ans*p[i]%mod;
	return ans;
}

node work(int x){
	node ans;
	for(int i=1;i<=num;++i)while(x%p[i]==0)x/=p[i],ans.a0[i]++;
	ans.p0=x;
	return ans;
}

void exgcd(int a,int b,int &x,int &y){
	if(!b){x=1,y=0;return;}
	int x0,y0;
	exgcd(b,a%b,x0,y0);
	x=y0,y=x0-(a/b)*y0;
}

node mul(node x,node y){
	node ans;
	ans.p0=x.p0*y.p0%mod;
	for(int i=1;i<=num;++i)ans.a0[i]=x.a0[i]+y.a0[i];
	return ans;
}

node div(node x,node y){
	node ans;
	int X,Y;
	exgcd(y.p0,mod,X,Y);
	ans.p0=x.p0*X%mod;
	for(int i=1;i<=num;++i)ans.a0[i]=x.a0[i]-y.a0[i];
	return ans;
}

void init(int n){
	int tmp=mod;
	for(int i=2;i*i<=tmp;++i){
		if(tmp%i==0){
			p[++num]=i;
			int cnt=0;
			while(tmp%i==0)tmp/=i,cnt++;
			a[num]=cnt;
		}
	}
	if(tmp>1){
		p[++num]=tmp;
		a[num]=1;
	}
	for(int i=2;i<=n;++i)fac[i]=mul(work(i),fac[i-1]);
}

int C(int n,int m){
	if(m>n)return 0;
	return calc(div(fac[n],mul(fac[m],fac[n-m])));
}

signed main(){
	int n=read();mod=read();int l=read(),r=read(),ans=0;
	init(n);
	for(int i=0;i<=n;++i){
		int tmp=((C(n-i,(l+n-i+1)/2)-C(n-i,(r+n-i)/2+1))%mod+mod)%mod;
		if(tmp<=0)continue;
		ans=(ans+C(n,i)*tmp%mod)%mod;
	}
	print((ans+mod)%mod);
	return 0;
}

到这里,卡特兰数的学习先告一段落,之后碰见有关的好题会进行补充。

posted @ 2022-10-14 22:19  Daidly  阅读(135)  评论(0)    收藏  举报