欢迎来访
首先看一下卡特兰数。若一个数列 h n h_n hn满足:
h n = ∑ i = 0 n − 1 h i ⋅ h n − 1 − i h_n = \sum_{i=0}^{n-1}h_i \cdot h_{n-1-i} hn=i=0∑n−1hi⋅hn−1−i
则称 h n h_n hn为卡特兰数列。
还有一种形式若:
h n = C 2 n n n + 1 h_n = \frac{C_{2n}^n}{n+1} hn=n+1C2nn
也称 h n h_n hn为卡特兰数列
那什么样的问题的答案是卡特兰数呢?
题目链接
若 n n n为 3 3 3时,满足条件的 01 01 01序列为:
000111
001011
001101
010101
010011
我们可以将 01 01 01序列转化到一个二维的平面当中,如果是 0 0 0则向右走,反之则向上走,因此我们会走到 ( n , n ) (n,n) (n,n)点,那么在平面中任意前缀序列中 0 0 0的个数都不少于 1 1 1代表的意思就是,对于途中的每一个点,横坐标要大于等于纵坐标,即路线不能越过 y = x y=x y=x这条线。
看一下以 n = 6 n=6 n=6的例子:
图中橙色与紫色满足条件。
那满足条件的路线一共有多少呢?直接求不是很好求,所以我们可以转化一下,用所有的路线减去不满足条件的路线,那不满足条件的路线有什么样的特点呢?
这样的路线上必然存在一点的纵坐标是大于横坐标的,即这样的路线一定经过了 y = x + 1 y=x+1 y=x+1这条线,以图中的褐色为例。我们将褐色的路线第一次经过 y = x + 1 y=x+1 y=x+1以后的路线关于 y = x + 1 y=x+1 y=x+1做轴对称,用黑色的线表示。我们可以发现所有不满足的条件的路线都可以通过这种方式将终点变为 ( n − 1 , n + 1 ) (n-1,n+1) (n−1,n+1),在这个例子中即为 ( 5 , 7 ) (5,7) (5,7)。所有的路线为 C 12 6 C_{12}^6 C126,因为一共有 12 12 12个空,要放 6 6 6个 0 0 0所以为 C 12 6 C_{12}^6 C126,同理:不满条件的路线为 C 12 5 C_{12}^5 C125。
所以在一般问题中即为:
C 2 n n − C 2 n n − 1 C_{2n}^n-C_{2n}^{n-1} C2nn−C2nn−1
在对其进行转化:
C 2 n n − C 2 n n − 1 = ( 2 n ) ! n ! n ! − ( 2 n ) ! ( n − 1 ) ! ( n + 1 ) ! = ( 2 n ) ! ( n + 1 ) − ( 2 n ) ! n n ! ( n + 1 ) ! = ( 2 n ) ! n ! ( n + 1 ) ! = ( 2 n ) ! n ! n ! ( n + 1 ) = C 2 n n n + 1 C_{2n}^n-C_{2n}^{n-1}= \frac{(2n)!}{n!n!} - \frac{(2n)!}{(n-1)!(n+1)!}= \frac{(2n)!(n+1)-(2n)!n}{n!(n+1)!}= \frac{(2n)!}{n!(n+1)!} = \frac{(2n)!}{n!n!(n+1)} = \frac{C_{2n}^n}{n+1} C2nn−C2nn−1=n!n!(2n)!−(n−1)!(n+1)!(2n)!=n!(n+1)!(2n)!(n+1)−(2n)!n=n!(n+1)!(2n)!=n!n!(n+1)(2n)!=n+1C2nn
转化后即为:
C 2 n n n + 1 \frac{C_{2n}^n}{n+1} n+1C2nn
正好为开始讲的卡特兰数的第二种方式。
#include
using namespace std;
typedef long long LL;
const int MOD = 1e9 + 7;
int n;
int qmi(int a, int b, int p) {
int res = 1;
while (b) {
if (b & 1) res = (LL)res * a % MOD;
b >>= 1;
a = (LL)a * a % MOD;
}
return res;
}
int main() {
cin >> n;
int a = 2 * n, b = n;
int res = 1;
// 求:2n * 2n-1 * ... * 2n-n+1
for (int i = a; i >= a - b + 1; i--)
res = (LL)res * i % MOD;
// 除以 n! 之所以可以用费马小定理求逆元是因为,MOD为质数,且i不是MOD的倍数
for (int i = b; i >= 1; i--)
res = (LL)res * qmi(i, MOD - 2, MOD) % MOD;
res = (LL)res * qmi(n + 1, MOD - 2, MOD) % MOD;
cout << res << endl;
return 0;
}
题目链接
以样例为例,一共有五种方式:
1push 2push 3push 3pop 2pop 1pop 序列为321
1push 1pop 2push 2pop 3push 3pop 序列为123
1push 2push 2pop 1pop 3push 3pop 序列为213
1push 1pop 2push 3push 3pop 2pop 序列为132
1push 2push 2pop 3push 3pop 1pop 序列为231
我们发现是没有 312 312 312这种序列的,因为 3 3 3先出栈,就意味着, 3 3 3曾经进栈,既然 3 3 3都进栈了,那就意味着, 1 1 1和 2 2 2已经进栈了,此时 2 2 2一定在 1 1 1上面,也就是更接近栈顶,所以 2 2 2一定会先比 1 1 1出栈,也就没有 312 312 312这种序列。
在 p u s h push push和 p o p pop pop的过程中必须满足:栈内有元素才能 p o p pop pop,也就是说任意 p u s h push push、 p o p pop pop序列的前缀中 p u s h push push的数量必须大于等于 p o p pop pop的数量,这样就与满足条件的 01 01 01序列问题一样了。
由于本题的数据范围很小所以求组合数时直接使用递推式: C n m = C n − 1 m + C n − 1 m − 1 C_n^m=C_{n-1}^m+C_{n-1}^{m-1} Cnm=Cn−1m+Cn−1m−1预处理出所有的组合数。
#include
using namespace std;
const int N = 40;
int n;
long long c[N][N];
void init() {
for (int i = 0; i < N; i++)
for (int j = 0; j <= i; j++)
if (!j) c[i][j] = 1;
else c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
}
int main() {
init();
cin >> n;
cout << c[2 * n][n] / (n + 1) << endl;
return 0;
}
题目链接(AcWing)
题目链接(LeetCode)
这个题刚做的时候是用dfs做的。但是在AcWing上只能过7个测试点,不过在LeetCode上还是能过的。
#include
#include
#include
using namespace std;
typedef long long LL;
const int N = 1010, MOD = 1e9 + 7;
int n;
int f[N][N];
LL dfs(int l ,int r) {
if (l >= r) return 1;
if (f[l][r] != -1) return f[l][r];
f[l][r] = 0;
for (int i = l; i <= r; i++) {
f[l][r] = (f[l][r] + dfs(l, i - 1) * dfs(i + 1, r)) % MOD;
}
return f[l][r];
}
int main() {
memset(f, -1, sizeof f);
cin >> n;
cout << dfs(1, n) << endl;
return 0;
}
C++
class Solution {
public:
int numTrees(int n) {
vector<vector<int>> a(n + 10, vector<int>(n + 10, -1));
return dfs(1, n, a);
}
int dfs(int l, int r, vector<vector<int>> &a) {
if (l >= r) return 1;
if (a[l][r] != -1) return a[l][r];
a[l][r] = 0;
for (int i = l; i <= r; i++) {
a[l][r] += dfs(l, i - 1, a) * dfs(i + 1, r, a);
}
return a[l][r];
}
};
Java
class Solution {
public int numTrees(int n) {
int[][] a = new int[n + 10][n + 10];
for (int i = 0; i <= n; i++)
for (int j = 0; j <= n; j++)
a[i][j] = -1;
return dfs(1, n, a);
}
public int dfs(int l, int r, int[][] a) {
if (l >= r) return 1;
if (a[l][r] != -1) return a[l][r];
a[l][r] = 0;
for (int i = l; i <= r; i++) {
a[l][r] += dfs(l, i - 1, a) * dfs(i + 1, r, a);
}
return a[l][r];
}
}
这个问题与有 n n n个相同的节点构成的不同形态的二叉树是等价的。
因为相同的节点构成的不同形态的二叉树的数量是固定的,而又因为本题是 B S T BST BST所以对于每一种形态上放整数 1 1 1~ n n n是唯一确定的。
闫氏DP分析法:
到这里就会发现本题答案也是卡特兰数。
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int N = 1010, MOD = 1e9 + 7;
int n;
int f[N];
int main() {
cin >> n;
f[0] = 1; // 这个边界不能写成0
for (int i = 1; i <= n; i++)
for (int j = 0; j < i; j++)
f[i] = (f[i] + (LL)f[j] * f[i - 1 - j]) % MOD;
cout << f[n] << endl;
return 0;
}
AcWing算法基础
AcWing笔试面试
大话数据结构