【算法讲11:卡特兰数】默慈金数 | 那罗延数 | 施罗德数
⌈ \lceil ⌈卡特兰数 ⌋ \rfloor ⌋Catalan Number
引入
- 鸡蛋饼 | 洛谷 P1976
一个圆上有 2 × N 2\times N 2×N 个不同的点。用 N N N 条线段将他们两两相连
要求:每个点都只能被一条线段相连,且线段都不相交。
求方案数取模一个质数。 N ≤ 2999 N\le 2999 N≤2999
思考
- 如果我们用 d p dp dp 去做,肯定会想到一个 O ( N 2 ) O(N^2) O(N2) 的方法,貌似可以过该题。
设 d p ( i ) dp(i) dp(i) 表示,有 2 i 2i 2i 个点,满足要求的方案数。
我们把这 2 i 2i 2i 个点按顺时针编号排好。
然后,我们以第一个点做基准,考虑该点和哪个点连线,对答案有多少方案数的贡献
容易想到,点 1 1 1 必须和 偶数号点相连 ,不然就会出现一侧只剩下奇数个点,无法满足要求。
假设它和 k k k 号点连接,那么一侧剩下 k − 2 k-2 k−2 个点,一侧剩下 2 i − k 2i-k 2i−k 个点。
这就产生了两个子问题。子问题和原问题只有规模的变化,是等价的。
因为只能选择偶数点,且和 d p dp dp 定义吻合,我们按一对一对来计算绘图:
一侧分为 k k k 对点,分的方案数为 d p ( k ) dp(k) dp(k);
另一侧分为 i − k − 1 i-k-1 i−k−1对点,分的方案数为 d p ( i − k − 1 ) dp(i-k-1) dp(i−k−1)
按照乘法原则,我们把他们乘起来就是 d p ( i ) dp(i) dp(i) 的一部分。
然后枚举所有划分情况,即可得出:
d p ( i ) = { 1 i = 0 ∑ n j = 1 d p ( j − 1 ) d p ( i − j ) i ≥ 1 dp(i)= \begin{cases} 1&i=0\\ \underset{j=1}{\overset{n}{\sum}}dp(j-1)dp(i-j)&i\ge1 \end{cases} dp(i)=⎩⎨⎧1j=1∑ndp(j−1)dp(i−j)i=0i≥1
⌈ \lceil ⌈卡特兰数 ⌋ \rfloor ⌋的性质
- 于是,我们得到了卡特兰数
我喜欢记作 C a t ( i ) Cat(i) Cat(i),也有记作 C a t a l a n ( i ) Catalan(i) Catalan(i) 与 C i C_i Ci 与 h i h_i hi 的。
根据百度百科 [1],我们得到了卡特兰数的一些性质:
- 定义式:
C a t ( i ) = { 1 i = 0 ∑ n j = 1 C a t ( j − 1 ) C a t ( i − j ) i ≥ 1 Cat(i)= \begin{cases} 1&i=0\\ \underset{j=1}{\overset{n}{\sum}}Cat(j-1)Cat(i-j)&i\ge1 \end{cases} Cat(i)=⎩⎨⎧1j=1∑nCat(j−1)Cat(i−j)i=0i≥1 - 递推式:
C a t ( n ) = C a t ( n − 1 ) 4 n − 2 n + 1 Cat(n)=Cat(n-1)\cfrac{4n-2}{n+1} Cat(n)=Cat(n−1)n+14n−2 - 关系式:
C a t ( n ) = C 2 n n n + 1 = C 2 n n − C 2 n n − 1 Cat(n)=\cfrac{C_{2n}^n}{n+1}=C_{2n}^n-C_{2n}^{n-1} Cat(n)=n+1C2nn=C2nn−C2nn−1
最残酷的是,这几个都要记住,每个式子都有自己的优点和缺点…
神奇的是,卡特兰数在一些看似不同的题目中均有出现。
⌈ \lceil ⌈卡特兰数 ⌋ \rfloor ⌋的应用
图片均出自wikipedia
- 常规分析法
就像上面引入的问题一样,每一步可以把大问题划分成两个小问题,然后乘起来求和。
【鸡蛋饼】 | 洛谷 P1976
【楼梯搭建】 | 洛谷 P2532
【凸多边形的三角划分问题】
【矩阵连乘方案数】
【 N N N个节点组成二叉树的方案数】 - 非常规分析法
你每次可以向右或向上移动一格,且不能跨过对角线,从 ( 0 , 0 ) (0,0) (0,0)走到 ( n , n ) (n,n) (n,n)的方案数
抽象地,如果向右移动一格记作 + 1 +1 +1,向上移动一格记作 − 1 -1 −1,那么卡特兰数还可以表示为:
每个元素为 ± 1 \pm1 ±1,且每一个前缀和都非负,且整个序列的和为 0 0 0 的长度为 n n n 的序列的个数。
.
【矩阵】 | 洛谷 P1722
红色看做 + 1 +1 +1 黑色看做 − 1 -1 −1,满足定义。
.
【栈】 | 洛谷 P1044
栈的进入和进出相当于 + 1 / − 1 +1/-1 +1/−1,前缀和非负表示栈内元素数非负,整个序列和为 0 0 0 表示所有元素都进、出栈完成。
.
【球迷购票】 | 洛谷 P1754
50 ¥ 50¥ 50¥ 相当于 + 1 +1 +1, 100 ¥ 100¥ 100¥ 要找零相当于 − 1 -1 −1,和定义相符。
`
【 n n n对括号的合法匹配个数】
( \color{cyan}( ( 看做 + 1 +1 +1, ) \color{cyan}) ) 看做 − 1 -1 −1,和定义相符。
.
【有趣的序列】 | 洛谷 P3200
这个问题,等价于:给定 2 × n 2\times n 2×n的矩阵,矩阵中的所有元素构成一个 1 ∼ 2 n 1\sim 2n 1∼2n的全排列
要求矩阵下边的元素大于上边的,右边的元素大于左边的。
求方案数。(这是什么?这是2020年蓝桥杯省赛的原题呀!)
非常规思考法:我们依次 1 ∼ 2 n 1\sim 2n 1∼2n 摆放,每次选择摆放上行或者下行。
要求上行个数每时每刻都要大于等于下行个数,那就设上行放一个元素即为 + 1 +1 +1,下行放为 − 1 -1 −1
然后又又又符合定义了,答案就是 C a t ( n ) Cat(n) Cat(n)
但是! 这题 n n n 比较大,模数又不是质数,我们目前阶段只能使用 3关系式 做(据说可以分治FFT做啥的?肯定我不懂了)。
答案就是 ∏ i = n + 2 2 n i ∏ i = 1 n i \cfrac{\prod_{i=n+2}^{2n}i}{\prod_{i=1}^ni} ∏i=1ni∏i=n+22ni
还是不好办,我们使用 欧拉筛 求出每个数的最小质因数,然后把每个数都分解为一个质数和另一个因数。于是答案最终会变成一些质数的乘积,因为每一个分母都会被某个分子给约掉。
const int MAX = 2e6+50;
ll qpow(ll a,ll n,ll p){
a%=p;ll res = 1LL;while(n){
if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
int ep[MAX],mp[MAX];
int vis[MAX],prime[MAX];
int cnt;
void shai(int n){
mp[1] = 1;
for(int i = 2; i <= n;++i){
if(!vis[i]){
prime[++cnt] = i;
mp[i] = i;
}
for(int j = 1;j <= cnt && i * prime[j] <= n;++j){
vis[i * prime[j]] = 1;
mp[i * prime[j]] = prime[j];
if(i % prime[j] == 0)break;
}
}
}
int main()
{
int n,p;
cin >> n >> p;
shai(n*2);
for(int i = 1;i <= n;++i)ep[i] = -1;
for(int i = n + 2;i <= 2 * n;++i)ep[i] = 1;
for(int i = n * 2;i > 1;--i){
if(mp[i] < i){
ep[mp[i]] += ep[i];
ep[i/mp[i]] += ep[i];
}
}
ll res = 1;
for(int i = 2;i <= n * 2;++i){
if(mp[i] == i){
res = res * qpow(i,ep[i],p) % p;
}
}
cout << res;
return 0;
}
⌈ \lceil ⌈默慈金数 ⌋ \rfloor ⌋Motzkin Number
性质
- 记作 M i M_i Mi
- 定义式:
M i = { 1 i = 1 2 i = 2 M i − 1 + ∑ i − 2 j = 0 M j M i − j − 2 i ≥ 3 M_i= \begin{cases} 1&i=1\\ 2&i=2\\ M_{i-1}+\underset{j=0}{\overset{i-2}{\sum}}M_{j}M_{i-j-2}&i\ge3 \end{cases} Mi=⎩⎪⎪⎪⎨⎪⎪⎪⎧12Mi−1+j=0∑i−2MjMi−j−2i=1i=2i≥3 - 递推式:
M n = ( 2 n + 1 ) M n − 1 + ( 3 n − 3 ) M n − 2 n + 2 M_n=\cfrac{(2n+1)M_{n-1}+(3n-3)M_{n-2}}{n+2} Mn=n+2(2n+1)Mn−1+(3n−3)Mn−2 - 关系式: 式子得到方法见下 [ 3 ]
M n = ∑ i = 0 ⌊ n 2 ⌋ C n 2 i C a t ( i ) M_n=\sum_{i=0}^{\lfloor\frac{n}{2}\rfloor}C_n^{2i}Cat(i) Mn=i=0∑⌊2n⌋Cn2iCat(i)
可以看到定义式和关系式都表示默慈金数和卡特兰数有某种联系
咋连一个定义都没有?因为在数学的各分支中,默慈金数至少有十四个彼此不同的展现存在[2]
应用
- 【弦划分】
一个圆上有 N N N 个不同的点。
画出彼此不相交的弦的方案数,即为 M N M_N MN
- 【Delta Wave】 | HDU 3723
- 从 ( 0 , 0 ) (0,0) (0,0) 到 ( N , 0 ) (N,0) (N,0),你每次可以向右上一格/向右下一格/向正右一格,且不能低于 y y y 轴
方案数就是 M N M_N MN 。
可以用关系式推导:假设有 x x x 条上升线段,则必有 x x x 条下降线段。
我们首先从 N N N 步中选出 2 x 2x 2x 步用于上升与下降,其余步数都是平右。方案数 C N 2 i C_{N}^{2i} CN2i
接下来,如果上升表示 + 1 +1 +1,下降表示 − 1 -1 −1,要求所有前缀和都非负,且总和为 0 0 0
这样满足 卡特兰数 的性质,故方案数为 C a t ( i ) Cat(i) Cat(i)
然后枚举所有可能性累加即可。[ 3 ] - 【计算】 | 51nod 1556
长度为 N N N 的序列,每个元素都为正整数。
第一个元素为 1 1 1,相邻两个数的差不超过 1 1 1。求这样的序列的方案数。
可以想到,默慈金数的要求:相邻差为 1 1 1 满足,非负要求满足,但是头和尾相同要求没有满足。
我们设 d p ( i ) dp(i) dp(i) 表示满足的答案, M i M_i Mi 表示默慈金数。
容易想到, d p ( i ) = 3 d p ( i − 1 ) dp(i)=3dp(i-1) dp(i)=3dp(i−1)再减去一些非法个数。
乘以三是因为每一步可以向三个方向选择。(蓝箭头)
非法个数是什么?就是第 i − 1 i-1 i−1 步,纵坐标正好为 1 1 1 的方案数。(红箭头为非法方案,即 M i − 2 M_{i-2} Mi−2)
得到了: d p ( i ) = 3 d p ( i − 1 ) − M i − 2 dp(i)=3dp(i-1)-M_{i-2} dp(i)=3dp(i−1)−Mi−2
ll M[MAX];
ll dp[MAX];
int main()
{
int n;
cin >> n;
M[1] = 1;
M[2] = 2;
dp[1] = 1;
dp[2] = 2;
for(int i = 3;i <= n;++i){
dp[i] = (3 * dp[i-1] - M[i-2]) % MOD;
M[i] = M[i-1] * (2*i+1) % MOD + (3*i-3) * M[i-2] % MOD;
M[i] = M[i] * inv(i + 2) % MOD;
}
dp[n] = (dp[n] + MOD) % MOD;
cout << dp[n];
return 0;
}
⌈ \lceil ⌈那罗延数 ⌋ \rfloor ⌋Narayana number
性质
- 记作 N ( n , k ) N(n,k) N(n,k) [3]
- 定义式:
N ( n , k ) = 1 n C n k C n k − 1 N(n,k)=\frac{1}{n}C_n^kC_n^{k-1} N(n,k)=n1CnkCnk−1 - 关系式:
∑ i = 1 n N ( n , i ) = C a t ( n ) \sum_{i=1}^nN(n,i)=Cat(n) i=1∑nN(n,i)=Cat(n)
那罗延数和卡特兰数的关系可以从关系式中看到。
看一下这张图:来源 [ 3 ]
可以看到, N ( n , k ) N(n,k) N(n,k) 表示你每次只能向右上一格/向右下一格,从 ( 0 , 0 ) (0,0) (0,0) 到 ( 2 N , 0 ) (2N,0) (2N,0),满足纵坐标非负要求,且峰 为 k k k 个的方案数。
所有的合法方案数,如果坐标系转九十度,等价为:
每次可以向右走一格/向上走一格, 从 ( 0 , 0 ) (0,0) (0,0) 到 ( n , n ) (n,n) (n,n) 不越过对角线的方案数
这就得到了该关系式。
应用
- 【括号匹配】
有 n n n 对括号,共有 k k k 对 ( \color{cyan}( ( 和 ) \color{cyan}) ) 相邻的合法括号序列方案数为 N ( n , k ) N(n,k) N(n,k)
⌈ \lceil ⌈施罗德数 ⌋ \rfloor ⌋Schröder Number
定义
- 施罗德数也叫大施罗德数,也叫超级卡特兰数。记作 S n S_n Sn
- 定义式:
S n = { 1 n = 0 或 n = 1 S n − 1 + ∑ i = 0 n − 1 S i S n − k − 1 n ≥ 2 S_n= \begin{cases} 1&n=0或n=1\\ S_{n-1}+\sum_{i=0}^{n-1}S_iS_{n-k-1}&n\ge2 \end{cases} Sn={ 1Sn−1+∑i=0n−1SiSn−k−1n=0或n=1n≥2 - 递推式:
S n = ( 6 n − 3 ) S n − 1 − ( n − 2 ) S n − 2 n + 1 S_n=\cfrac{(6n-3)S_{n-1}-(n-2)S_{n-2}}{n+1} Sn=n+1(6n−3)Sn−1−(n−2)Sn−2
应用
- 【又是走格子】
( 0 , 0 ) (0,0) (0,0) 走到 ( n , n ) (n,n) (n,n)的格子,每次可以向右上走一格/向右走一格/向上走一格,且不超过对角线的方案数。 [ 4 ]
总结
- 卡特兰数 最常用到
默慈金数、那罗延数、施罗德数是卡特兰数的拓展 - 每者的几何意义都有和走格子有关:
卡特兰数 C a t ( n ) Cat(n) Cat(n) : ( 0 , 0 ) (0,0) (0,0) 走到 ( n , n ) (n,n) (n,n),移动规则为 ( 1 , 0 ) 、 ( 0 , 1 ) (1,0)、(0,1) (1,0)、(0,1) 且不穿过对角线的方案数
施罗德数 S n S_n Sn : ( 0 , 0 ) (0,0) (0,0) 走到 ( n , n ) (n,n) (n,n),移动规则为 ( 1 , 0 ) 、 ( 0 , 1 ) 、 ( 1 , 1 ) (1,0)、(0,1)、(1,1) (1,0)、(0,1)、(1,1) 且不穿过对角线的方案数
默慈金数 M n M_n Mn : ( 0 , 0 ) (0,0) (0,0) 走到 ( n , 0 ) (n,0) (n,0),移动规则为 ( 1 , 0 ) 、 ( 1 , − 1 ) 、 ( 1 , 1 ) (1,0)、(1,-1)、(1,1) (1,0)、(1,−1)、(1,1) 且不穿过 x x x 轴的方案数
那罗延数 N ( n , k ) N(n,k) N(n,k) : ( 0 , 0 ) (0,0) (0,0) 走到 ( 2 n , 0 ) (2n,0) (2n,0),移动规则为 ( 1 , 1 ) 、 ( 1 , − 1 ) (1,1)、(1,-1) (1,1)、(1,−1) 且不穿过 x x x 轴,有 k k k 个峰的方案数