一、前置知识
在笛卡尔坐标系中,起点为((0,0)),终点为((n,m)),每一步只能向上或向右走的方案数为(C_{n+m}^n)。
二、基本卡特兰数
首先亮出卡特兰数的基本公式:
那么卡特兰数有许多经典问题,我们来从最经典的入手。
例一:在平面直角坐标系中,起点为((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)。((mgeq 1))
有了刚才的思路,我们很容易想到把违法的路径从点A到终点的部分围绕直线(y = x - m - 1)翻折,可以得到从原点到((n - m - 1, n +m + 1))的路径。同样,两者构成一一对应关系。
最终的答案即为(C_{2n}^{n} - C_{2n}^{n - m - 1})。
四、应用
看一道真实的考试题。
如果我们把左括号看成上文例题中的“向上走一步”,右括号看成“向右走一步”,我们会发现本题中所说的“至少有(2 imes 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
", C(2 * n, n) * power(n + 1, mod - 2) % mod), 0;
return printf("%lld
", ((excatalan(n, m) - excatalan(n, m - 1)) % mod + mod) % mod), 0;
}