前言:
今天发现了一道题,认认真真的以为是组合数,认认真真的看了半天,认认真真的懵逼了好久,最后去看了题解,原来这个玩意儿是卡特兰数······
内容:
卡特兰数的数值h(n)为1 ~ n这n个数,按照从小到大的顺序入栈,不同的出栈序列种数。
公式:
1、h(i) = c(2n, n) - c(2n, n - 1)
证明:我们来形象一点考虑这个序列的意义,我们把入栈看做I,出栈看做O,那么相当于我们现在有一个长度为2 * n的IO序列,并且对于这个序列的每一个前缀都应该满足I的个数大于O的个数,(入栈数大于等于出栈数),那么首先,我们先不考虑后一个前缀中的个数限制,那么总的I有n个,O有n个的情况一定是C(2n,n)个,然后考虑,非法的情况,首先我们还是保证这个非法的序列有n个I,n个O,那么显然的,因为它是非法的,那么一定存在某个位置x,满足x是一个奇数,并且是第一个满足序列1 ~ x中的O的个数比I的个数大1的位置,那么考虑这种情况意味着什么,如果,我们将1 ~ x中的I全部变成O,O全部变成I,那么原来的序列,会变成一个有着n +1个I,有n - 1个O的序列,并且显然,任意不同的非法序列,都一定能对应不同的有着n - 1个O,n + 1个I的序列,我们在来反向考虑,这样的序列是不是也一定能够对应一个非法序列,显然对于一个满足有n - 1个O,n + 1个I的序列,我们一定能找到一个位置x,满足x为奇数,并且,x是第一个满足1 ~ x中,I的数量比O的数量多1的(显然I的总数大于O的总数),那么我们将这一段中的I和O反转,就形成了一个有着n个O,n个I的非法序列,所以说最终的合法的序列种数为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就可以获得上面的表达式了。
3、h(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)
4、h(n) = h(n - 1) * (4 * n - 2) / (n + 1)
证明:比较形象的证明我是没有找到的,但是可以直接粗暴的从式子1恒等证明回来,所以姑且算作是纯数学推导吧,具体的只需要证明c(2n - 2, n - 1) * (4 * n - 2) = c(2n, n)就可以了。
例题:有趣的数列
题目背景:
分析:卡特兰数 + 取膜
一共2 * n个数,n个奇数项,n个偶数项,一个长度为2 * n,1 ~ 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;
}