卡特兰数基础应用
卡特兰数基础应用
常见组合意义及推导
\(Cat_n\) 的常见组合意义:
- 由 \(n\) 个左括号与 \(n\) 个右括号组成的合法括号序列数量。
- 由 \(n\) 个 \(1\) 与 \(n\) 个 \(-1\) 组成的任意前缀和不小于 \(0\) 的序列数量。
抽象一下:有 \(n\) 个满足性质 \(P_1\) 的元素,\(n\) 个满足性质 \(P_2\) 的元素,将它们排成一个序列,任意前缀中满足 \(P_1\) 的元素不少于满足 \(P_2\) 的元素的序列数。
- \(n \times n\) 的网格图,从 \((0, 0)\) 出发,要走到 \((n, n)\),每次只能向右或向下走,求出不跨过直线 \(y = x\) 的路径条数。(将“向右”看作 \(P_1\),“向上”看作 \(P_2\) 即可)
- \(n\) 个点的形态不同的二叉树个数。
- 在一个 \(2n\) 个点的环上,将这些点两两连接,使 \(n\) 条连线不相交的方案数。(断环为链考虑)
容斥
设长度为 \(2n\) 的合法括号序列数为 \(T_n\)。
枚举第一个不合法的位置 \(2i + 1\)(显然第一个不合法位置不可能为偶数),则可以确定以下事实:前缀 \([1, 2i]\) 合法;\(s_{2i + 1}\) 为右括号;后缀 \([2i + 2, 2n]\) 中有 \(n - i\) 个左括号,\(n - i - 1\) 个右括号。后缀中的括号可以随便排列,于是得到
依照上式计算前 \(n\) 项的复杂度为 \(O(n^2)\)。不太会卷积。
这个式子似乎推通项比较困难(。
圆内不相交弦计数
考虑圆内不相交弦的计数:记 \(2n\) 个点,两两配对使得 \(n\) 条线段不相交的方案数为 \(T_n\),从任意点出发顺时针编号,显然 \(1\) 号点只能连偶数号点,否则必有 \(2\) 或 \(2n\) 为一端的线段与其相交;设 \(1\) 连了 \(2k\),则左侧还有 \(2k - 2\) 个点需要内部配对,右侧有 \(2n - 2k\) 个点,由乘法原理可知方案数是 \(T_{k - 1}T_{n - k}\),故
这即是卡特兰数的递推公式。
网格图
考虑网格图的组合意义:对于每条穿过了 \(y = x\) 这条直线的路径,将其第一个与 \(y = x + 1\) 的交点之后的路径关于 \(y = x + 1\) 对称过去,其一定终止于 \((n - 1, n + 1)\),这样的路径总条数有 \(\dbinom{2n}{n - 1}\) 条,于是 \(T_n = \dbinom{2n}{n} - \dbinom{2n}{n - 1} = \dfrac{1}{n + 1}\dbinom{2n}{n}\)。
从网格图出发也可以得出递推公式:考虑在 \((0, 0)\) 时,必须向右走到 \((1, 0)\),此后一定在某一处 \((k, k)\) 回到 \(y = x\) 直线上(但不跨过,此处 \(k\) 满足 \(1 \le k \le n\)),那么问题就被拆分为两个子问题:从 \((0, 0)\) 走到 \((1, 0)\),然后满足限制走到 \((k, k - 1)\),再走到 \((k, k)\);再从 \((k, k)\) 满足限制走到 \((n, n)\)。显然后一个问题等价于从 \((0, 0)\) 走到 \((n - k, n - k)\),而前一个问题容易注意到就是向右平移了一个单位长度的 \((0, 0) \to (k - 1, k - 1)\) 子问题,故可以得出
与上面得到的结果相同。
Raney 引理
引理 1:对于一个 \(x_i = \pm 1\) 且 \(\sum x_i = 0\) 的序列,一定存在至少一种循环移位使得所有的前缀和都 \(\ge 0\)。
设前缀和数组为 \(\{s_{2n}\}\),若其已经满足非负就不用移位了;否则找到其最小值 \(s_i\),若有多个我们取最靠后的一个。由于 \(s_{2n} = 0\),所以 \(s_{2n} - s_i = -s_i \gt 0\),设循环移位后前缀和数组变为 \(\{s'_{2n}\}\),于是我们直接将 \([i + 1, 2n]\) 这一段移到开头去就能够满足 \(\forall j \in [2n - i + 1, 2n], s'_j \ge 0\),并且由于我们刚才取的是最远的最小值,我们循环移位到开头的这一段能够保证任意前缀和 \(\gt 0\),于是整个移位后的序列就满足条件了。
显然这可以扩展到 \(x_i\) 取任意整数的情况。
引理 2:对于一个 \(x_i = \pm 1\) 且 \(\sum x_i = 1\) 的序列,一定存在恰好一种循环移位使得所有的前缀和都 \(\gt 0\)。
我们考虑在引理 1 的序列末尾加入一个 \(x_{2n + 1} = 1\),然后考虑我们应该把它循环移位到哪个位置。显然,我们可以沿用刚才的策略:找到最远的前缀和最小值 \(s_i\),将 \([i + 1, 2n + 1]\) 移动到序列开头。注意到我们可以保证这一段前缀和 \(\gt 0\),且由于多出了一个 \(1\),现在 \(\forall j \in [2n - i + 2, 2n + 1], s'_j \ge 1\),也满足了条件。
注意到引理 1 中的“至少”在这里变成了“恰好”,其实这是好理解的:假如有 \(m\) 个前缀和最小值的位置 \(1 \le p_1 \lt p_2 \lt \dotsb \lt p_{m - 1} \lt p_m =i\),若我们选择将 \([p_k + 1, 2n + 1]\)(\(k \lt m\))移动到序列开头,那么必有 \(s'_{p_{k + 1} - p_k - 2n} = 0\),不能满足条件。所以这里有且仅有一种移位的方法。
显然这也可以扩展到 \(x_i\) 为任意整数的情况。
而这被称作 Raney 引理:
对于一个 \(\sum x_i = 1, x_i \in \Z\) 的序列,一定存在恰好一种循环移位使得所有的前缀和都 \(\gt 0\)。
重新考虑卡特兰数的 \(\pm 1\) 序列组合意义:长度为 \(2n\),\(x_i = \pm 1, \sum x_i = 0\),所有前缀和都 \(\ge 0\) 的序列数。由于引理 1 是“至少”,所以我们向引理 2 转化:在每个合法序列开头插入一个 \(1\),那么问题就变为求出长度为 \(2n + 1\),\(x_1 = 1\),\(x_i = \pm 1, \sum x_i = 1\),所有前缀和都 \(\gt 0\) 的序列数。
考虑到第一个数是 \(1\) 的序列总个数是 \(\dbinom{2n}{n}\),而对于每个合法的序列,我们将之后的每个 \(1\) 循环移位到最前面,根据引理 2,得到的序列本身一定不合法(因为它有且仅有一种循环移位合法,而这种移位就是刚才的逆操作),所以每个合法序列会产生 \(n\) 个不合法序列,也就是说每 \(n + 1\) 个序列中有且仅有一个合法,所以 \(T_n = \dfrac{\dbinom{2n}{n}}{n + 1}\)。
例题
P3200
题意:求长度为 \(2n\) 的排列中,满足所有奇数项严格递增、所有偶数项严格递增,且 \(\forall i \in [1, n], a_{2i - 1} \lt a_{2i}\) 的数量,对 \(p\) 取模。\(n \le 10^6, p \le 10^9\)。
考虑用 \(1 \sim 2n\) 去填 \(2n\) 个空位的过程,若将奇数位置空位和偶数位置空位区分开,不难发现需要任意时刻满足填入的前者不少于后者(这样才能满足 \(a_{2i - 1} \lt a_{2i}\) 的限制),这就是卡特兰数的一种组合意义,所以答案就是 \(Cat_n\)。
本题的难点在于 \(p\) 不一定是质数,这导致分母的逆元不一定存在。考虑唯一分解定理,对每个质因数单独计算即可。
P5014
题意:给定无穷大的网格图,每次可以向右、向下或向右下走,求出从 \((0, 0)\) 走到 \((n, m)\) 不穿过直线 \(y = x\) 的方案数,对 \(998244353\) 取模。有 \(T\) 组询问。\(T \le 100, n, m \le 7 \times 10^6\)。
考虑枚举向右下走了 \(i\) 次,显然这种移动不会改变路径与直线 \(y = x\) 的相对关系,所以把这些斜着的轨迹去掉后,剩下的轨迹可以重新拼成一条从 \((0, 0)\) 出发,只能向右或向下,不穿过直线 \(y = x\) 走到 \((n - i, m - i)\) 的路径,利用和卡特兰数在网格图上的推导一样的思路,将从 \((0, 0)\) 走到 \((x, y)\) 的每条不合法路径关于 \(y = x + 1\) 对称,则其一定终止于 \((y - 1, x + 1)\),于是答案是
时间复杂度 \(O(Tm)\)。
CF1528F
序列 \(\to\) 等概率环模型:
长为 \(n\),值域为 \(n\) 的序列。想象一排 \(n\) 个座位,每次第 \(i\) 个人从 \(a_i\) 出发,沿某个(左 / 右任意)方向走到第一个未被占的位置,并在这个位置坐下。一个序列合法当且仅当按顺序进入 \(n\) 个人后位置 \(1 \sim n\) 都恰有一个人。
那么将其扩展一项 \(n + 1\) 成为环后,合法的判定转变为 最终只有 \(n + 1\) 是空位。
这是显然的,因为它是空位说明它从未被经过,也就是说任何的考虑都没有超出序列原本的范围,转换正确。
由于进入环上每个点的概率相同 \(^{\nabla}\),所以环上每个点作为终止态的概率也相同。于是合法序列的个数可以直接算出,是 \(\dfrac{1}{n+1} \times (n+1)^N = (n + 1)^{N - 1}\)。
对于 \(N \le n\) 个人,环上最终的空位个数是 \(n - N + 1\),那么 \(n + 1\) 空出来的概率就是 \(\dfrac{n - N + 1}{n + 1}\)。
但是这还不够。考虑给序列加上一个与序列结构有关而与具体每项无关的权值。例如本题是 \(\sum \limits_{x = 1}^{n + 1} cnt^k(x)\),即每种数出现次数的 \(k\) 次方和。
注意这里需要保证 \({\nabla}\),所以我们把值域扩充到了 \(n + 1\)。由于只要序列存在 \(n + 1\) 一定不合法,所以只要保证之后只计算了合法序列的权值,就是正确的。
构造映射:设序列 \(a\) 合法,若取一个 \(x \in [1, n]\),\(\forall a_i \leftarrow (a_i + x - 1) \bmod{ (n + 1)} + 1\),那么最终空位 \(fin\) 也会 \(fin \leftarrow (fin + x - 1) \bmod {(n + 1)} + 1\),这时这个序列就是不合法序列了。那么 一个合法序列可以映射到 \(n\) 个不合法序列,并且它们的权值都相等(只与 \(cnt\) 数组有关,而 \(cnt\) 数组取值的可重集没有发生变化)。
所以我们可以算出所有序列的权值和,再 \(\times \dfrac{1}{n + 1}\) 即为答案。
考虑如何算这个权值,每种数的贡献是一样的,可以算一种数最后再乘上 \(n + 1\)。
那么就是
这里 \((n + 1) - 1\) 是剩余其它数的个数,\(\dfrac{1}{n + 1}\) 是合法序列的系数。化简后即为
注意有个固定次数的普通幂,转成斯特林数:
补一个 \(j!\) 凑组合数:
然后求一行第二类斯特林数,直接枚举计算就可以做到 \(O(k)\) 了。
关于同一行的第二类斯特林数:
做一次卷积即可,复杂度为 \(O(n \log n)\)。
using ll = long long; using ui = unsigned int;
constexpr int N = 524288;
constexpr ui p = 998244353, G = 3, Gi = 332748118;
inline ui Sqr(ui x) { return 1ull * x * x % p; }
inline ui neg(ui x) { return x ? p - x : 0; }
inline ui P(ui &x, ui y) { return (x += y) >= p && (x -= p), x; }
inline ui Pow(ui bs, ui b, ui rs = 1) { for(; b; bs = Sqr(bs), b >>= 1) if(b & 1) rs = 1ull * rs * bs % p; return rs; }
struct Poly {
int len, lim; ui Inv, R[N], Gn[33], iGn[33];
ui fac[N], invf[N], strl[N], help[N];
Poly() {
fac[0] = fac[1] = invf[0] = invf[1] = 1;
for(int i = 2; i < N; ++i) fac[i] = 1ull * fac[i - 1] * i % p;
invf[N - 1] = Pow(fac[N - 1], p - 2);
for(int i = N - 1; i >= 3; --i) invf[i - 1] = 1ull * invf[i] * i % p;
}
inline int Get(int n) { for(len = 0, lim = 1; lim <= n; ++len, lim <<= 1); return len; }
inline void Prepare(int Len) {
Inv = Pow(lim = 1 << (len = Len), p - 2);
Gn[len] = Pow(G, (p - 1) / lim), iGn[len] = Pow(Gn[len], p - 2);
for(int i = len - 1; i; --i) Gn[i] = Sqr(Gn[i + 1]), iGn[i] = Sqr(iGn[i + 1]);
for(int i = 1; i < lim; ++i) R[i] = (R[i >> 1] >> 1) ^ ((i & 1) << len - 1);
}
inline void NTT(ui *a, bool type) {
for(int i = 1; i < lim; ++i) i < R[i] && (swap(a[i], a[R[i]]), 1);
for(int mid = 1, t = 1; mid < lim; mid <<= 1, ++t) {
ui gn = (type ? Gn : iGn)[t], gni = 1, x, y;
for(int i = 0; i < lim; i += mid << 1, gni = 1)
for(int j = 0; j < mid; ++j, gni = 1ull * gni * gn % p)
x = a[i + j], y = 1ull * a[i + mid + j] * gni % p,
P(a[i + j] = x, y), P(a[i + mid + j] = x, p - y);
}
}
inline void Mul(ui *a, ui *b) {
for(int i = 0; i < lim; ++i)
a[i] = 1ull * a[i] * b[i] % p;
}
inline void DFT(ui *a, int Len = 0) {
Len && len != Len && (Prepare(Len), 1), NTT(a, 1);
}
inline void IDFT(ui *a) {
NTT(a, 0);
for(int i = 0; i < lim; ++i)
a[i] = 1ull * a[i] * Inv % p;
}
inline void MUL(ui *a, ui *b, int n, int m) {
Prepare(Get(n + m));
DFT(a), DFT(b), Mul(a, b), IDFT(a);
}
inline void StirlingII_Row(int n) {
for(int i = 0; i <= n; ++i)
strl[i] = 1ull * Pow(i, n) * invf[i] % p,
help[i] = (i & 1) ? neg(invf[i]) : invf[i];
MUL(strl, help, n, n); // ...
}
} poly; int n, K;
main() {
ios::sync_with_stdio(0), cout.tie(0);
cin >> n >> K, poly.StirlingII_Row(K);
ui t1 = 1, t2 = Pow(n + 1, n), t3 = Pow(n + 1, p - 2), res = 0;
for(int i = 0; i <= K; ++i)
P(res, 1ull * poly.strl[i] * t1 % p * t2 % p),
t1 = 1ull * t1 * n % p, --n, t2 = 1ull * t2 * t3 % p;
cout << res << '\n';
}

浙公网安备 33010602011771号