卡特兰数

  卡特兰数学习总结

 

前言:

今天发现了一道题,认认真真的以为是组合数,认认真真的看了半天,认认真真的懵逼了好久,最后去看了题解,原来这个玩意儿是卡特兰数······

 

内容:

卡特兰数的数值h(n)1 ~ nn个数,按照从小到大的顺序入栈,不同的出栈序列种数。

 

公式:

1h(i) = c(2n, n) - c(2n, n - 1)

证明:我们来形象一点考虑这个序列的意义,我们把入栈看做I,出栈看做O,那么相当于我们现在有一个长度为2 * nIO序列,并且对于这个序列的每一个前缀都应该满足I的个数大于O的个数,(入栈数大于等于出栈数),那么首先,我们先不考虑后一个前缀中的个数限制,那么总的In个,On个的情况一定是C(2n,n)个,然后考虑,非法的情况,首先我们还是保证这个非法的序列有nInO,那么显然的,因为它是非法的,那么一定存在某个位置x,满足x是一个奇数,并且是第一个满足序列1 ~ x中的O的个数比I的个数大1的位置,那么考虑这种情况意味着什么,如果,我们将1 ~ x中的I全部变成OO全部变成I,那么原来的序列,会变成一个有着n +1I,有n - 1O的序列,并且显然,任意不同的非法序列,都一定能对应不同的有着n - 1On + 1I的序列,我们在来反向考虑,这样的序列是不是也一定能够对应一个非法序列,显然对于一个满足有n - 1On + 1I的序列,我们一定能找到一个位置x,满足x为奇数,并且,x是第一个满足1 ~ x中,I的数量比O的数量多1的(显然I的总数大于O的总数),那么我们将这一段中的IO反转,就形成了一个有着nOnI的非法序列,所以说最终的合法的序列种数为C(2n,n) - C(2n, n - 1)个,得证。

 

2

证明:我们还是在定义的基础上来搞一搞,设我们最后出栈的数为k,那么k -1一定在k入栈之前就已经出栈,也就是说k入栈的时候,栈为空,那么1 ~ k- 1出栈序列有h(k- 1)中,同样的,因为,k为最后出栈的,所以在k入栈之后,k + 1 ~ n全部入栈并且出栈之后,k最后出栈,那么k +1 ~ n的出栈序列有h(n- k)种,那么我们枚举k就可以获得上面的表达式了。

 

3h(n) = c(2n, n) / (n + 1)

证明:我们知道,c(2n,n) * n / (n + 1) = c(2n, n - 1),那么显然,从递推式一可以得到,h(n) = c(2n, n) - c(2n, n) * n / (n + 1) = c(2n, n) / (n + 1)

 

4h(n) = h(n - 1) * (4 * n - 2) / (n + 1)

证明:比较形象的证明我是没有找到的,但是可以直接粗暴的从式子1恒等证明回来,所以姑且算作是纯数学推导吧,具体的只需要证明c(2n - 2, n - 1) * (4 * n - 2) = c(2n, n)就可以了。

 

例题:有趣的数列

题目背景:

bzoj1485

分析:卡特兰数 + 取膜

一共2 * n个数,n个奇数项,n个偶数项,一个长度为2 * n1 ~ 2 * n的序列,每一个前缀中的奇数项一定大于等于偶数项,那么也就是可以想成是n个数进栈,求出栈序列,也就是卡特兰数了,然后我们要求的就是c(2n, n) - c(2n, n - 1)直接暴力分解质因数就可以了,复杂度O(nlogn)

Source

/*
	created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>

const int MAXN = 1000000 + 10;

int n, mod, prime_cnt;
int up[MAXN], down[MAXN], prime[MAXN];
bool not_prime[MAXN];

inline void seive(int n) {
	not_prime[1] = true;
	for (int i = 2; i <= n; ++i) {
		if (!not_prime[i]) prime[++prime_cnt] = i;
		for (int j = 1; j <= prime_cnt && prime[j] * i <= n; ++j) {
			not_prime[i * prime[j]] = true;
			if (i % prime[j] == 0) break;
		}
	}
}

inline int solve(int n, int m) {
	static int cnt[MAXN];
	for (int i = 1; i <= m; ++i) down[i] = i, up[i] = n - i + 1;
	for (int i = 1; i <= prime_cnt; ++i) {
		int x = prime[i];
		for (int j = x; j <= m; j += x)
			while (down[j] % x == 0) cnt[x]++, down[j] /= x;
		for (int j = n % x + 1; cnt[x]; j += x) 
			while (up[j] % x == 0 && cnt[x]) cnt[x]--, up[j] /= x;
	}
	int ans = 1;
	for (int i = 1; i <= m; ++i) ans = (long long)ans * (long long)up[i] % mod;
	return ans;
}

int main() {
	scanf("%d%d", &n, &mod), seive(n);
	printf("%d", (solve(2 * n, n) - solve(2 * n, n - 1) + mod) % mod);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值