卡特兰数相关总结

一、前置知识

在笛卡尔坐标系中,起点为\((0,0)\),终点为\((n,m)\),每一步只能向上或向右走的方案数为\(C_{n+m}^n\)

二、基本卡特兰数

首先亮出卡特兰数的基本公式:

\[Cat_n = \frac{C_{2n}^n}{n + 1} \]

那么卡特兰数有许多经典问题,我们来从最经典的入手。

例一:在平面直角坐标系中,起点为\((0,0)\),终点为\((n,n)\),第一步只能向上走。在之后的移动中可以向上走或向右走,但不能超出直线\(y=x\) 。求方案数。

首先这个方案数可以表示为从(0,0)走到(n,n)的总方案数减去至少超出一次的方案数。

从(0,0)走到(n,n)的总方案数即为\(C_{2n}^n\).

现在考虑至少超出一次的方案数。当我们刚刚超出直线\(y = x\)的时候,我们一定处于直线\(y = x - 1\)上,把此时所在的点记为点A。现在我们做这样一件事情,把原本从点A到终点的路径围绕直线\(y = x - 1\)翻折,那么这条路径就变成从原点到点\((n+1,n-1)\)的一条路径。

我们又可以发现,所有违法的路径与所有从原点到\((n + 1, n - 1)\)的路径构成一一对应关系。证明极其简单,在脑子里想一下就行了。

所以至少超出一次的方案数就是\(C_{(n+1)+(n-1)}^{n+1}=C_{2n}^{n+1}\)

最终的答案即为\(C_{2n}^n - C_{2n}^{n+1}\)。化简后得到\(\frac{C_{2n}^n}{n + 1}\)

三、扩展卡特兰数

例二:其他条件与例一相同,唯一不一样的是把直线\(y = x\) 改为直线\(y = x - m\)\((m\geq 1)\)

有了刚才的思路,我们很容易想到把违法的路径从点A到终点的部分围绕直线\(y = x - m - 1\)翻折,可以得到从原点到\((n - m - 1, n +m + 1)\)的路径。同样,两者构成一一对应关系。

最终的答案即为\(C_{2n}^{n} - C_{2n}^{n - m - 1}\)

四、应用

看一道真实的考试题。

如果我们把左括号看成上文例题中的“向上走一步”,右括号看成“向右走一步”,我们会发现本题中所说的“至少有\(2\times m\)个括号失配”就是“在走的过程中,接触过直线\(y=x-m\)的一条路径”。注意:是“接触过直线\(y=x-m\)”,不能超出,也不能不接触。

所以方案数就应该为“不超过\(y=x- m\)”减去“不超过\(y=x-m-1\)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
#define LL long long
#define FILEIN(s) freopen(s".in", "r", stdin)
#define FILEOUT(s) freopen(s".out", "w", stdout)
#define mem(s, v) memset(s, v, sizeof(s))

inline LL read(void) {
	LL x = 0, f = 1; char ch = getchar();
	while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
	while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return f * x;
}

const int maxn = 1000005, mod = 998244353;

LL n, m;
LL fac[maxn << 1], facinv[maxn << 1];

inline LL power(LL a, LL b) {
	LL ret = 1;
	for (; b; b >>= 1) {
		if (b & 1) ret = ret * a % mod;
		a = a * a % mod;
	}
	return ret;
}

inline void init(void) {
	fac[0] = 1;
	for (register int i = 1; i <= 2000000; ++i) fac[i] = fac[i - 1] * i % mod;
	facinv[2000000] = power(fac[2000000], mod - 2);
	for (register int i = 1999999; i >= 0; --i) {
		facinv[i] = facinv[i + 1] * (i + 1) % mod;
	}
}

inline LL C(LL n, LL m) {
	return fac[n] * facinv[m] % mod * facinv[n - m] % mod;
}

inline LL excatalan(LL n, LL m) {
	return ((C(2 * n, n) - C(2 * n, n - m - 1)) % mod + mod) % mod;
}

int main() {
	n = read(); m = read();
	init();
	if (!m) return printf("%lld\n", C(2 * n, n) * power(n + 1, mod - 2) % mod), 0;
	return printf("%lld\n", ((excatalan(n, m) - excatalan(n, m - 1)) % mod + mod) % mod), 0;
}

posted @ 2019-10-30 11:43  蓝田日暖玉生烟  阅读(228)  评论(0编辑  收藏  举报