• 卡特兰数相关总结


    一、前置知识

    在笛卡尔坐标系中,起点为((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)((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;
    }
    
    
  • 相关阅读:
    idea 使用jedis连接不上 redis解决办法
    EFCore 连接 MySql 间歇性报错:你的主机中的软件中止了一个已建立的连接
    初学Ansible(管理Window主机)
    茹炳晟-API自动化测试笔记
    Xpath路径
    kibana Dev Tools --常用命令
    kibana Dev Tools--修改语句示例
    kibana Dev Tools--增删改查语句
    记一次出名记录
    脚本:bat实现自动转换windows远程端口
  • 原文地址:https://www.cnblogs.com/little-aztl/p/11763966.html
Copyright © 2020-2023  润新知