赞
踩
\qquad 卡特兰数(英语:Catalan number),又称卡塔兰数、明安图数,是组合数学中一种常出现于各种计数问题中的数列。以比利时的数学家欧仁·查理·卡特兰的名字来命名。1730年左右被蒙古族数学家明安图使用于对三角函数幂级数的推导而首次发现,1774年被发表在《割圜密率捷法》。
\qquad 给定 n n n 个 0 0 0 和 n n n 个 1 1 1,它们将按照某种顺序排成长度为 2 n 2n 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0 0 0 的个数都不少于 1 1 1 的个数的序列有多少个。这个求解的数字就被称作卡特兰数。
\qquad
记这个数列为
C
n
C_n
Cn。
C
n
=
C
2
n
n
−
C
2
n
n
−
1
=
C
2
n
n
n
+
1
C_n=C_{2n}^n-C_{2n}^{n-1}=\frac{C_{2n}^{n}}{n+1}
Cn=C2nn−C2nn−1=n+1C2nn
\qquad
它还满足以下递推关系:
1.
C
n
=
∑
i
=
1
n
C
i
−
1
×
C
n
−
i
=
C
0
C
n
−
1
+
C
1
C
n
−
2
+
⋯
+
C
n
−
1
C
0
2.
(
n
−
3
)
C
n
=
n
2
(
C
3
C
n
−
1
+
C
4
C
n
−
2
+
C
5
C
n
−
3
+
⋯
+
C
n
−
2
C
4
+
C
n
−
1
C
3
)
3.
C
n
+
1
=
4
n
+
2
n
+
1
⋅
C
n
1.\;C_{n}=\sum\limits_{i=1}^{n}C_{i-1}\times C_{n-i}=C_0C_{n-1}+C_1C_{n-2}+\cdots+C_{n-1}C_0 \\ 2.\;(n-3)C_n=\frac{n}{2}(C_3C_{n-1}+C_4C_{n-2}+C_5C_{n-3}+\cdots+C_{n-2}C_4+C_{n-1}C_3) \\ 3.C_{n+1}=\frac{4n+2}{n+1}\cdot C_n
1.Cn=i=1∑nCi−1×Cn−i=C0Cn−1+C1Cn−2+⋯+Cn−1C02.(n−3)Cn=2n(C3Cn−1+C4Cn−2+C5Cn−3+⋯+Cn−2C4+Cn−1C3)3.Cn+1=n+14n+2⋅Cn
\qquad
首先我们需要将上述问题转换成一个等价的问题:在一个二维平面内,从 (0, 0)
出发到达 (n, n)
,每次可以向上或者向右走一格,**0 代表向右走一格,1 代表向上走一格,**则每条路径都会代表一个 01 序列。
\qquad 我们可以先计算出所有的路径条数应该为 C 2 n n C_{2n}^{n} C2nn 条。这可以理解为:你一共需要走 2 n 2n 2n 步,其中选择 n n n 步向上走,剩下 n n n 步自然是向右走。
\qquad 根据题意,满足任意前缀中 0 的个数不少于 1 个数序列对应的路径则应该在下图的右下侧。
\qquad 符合要求的路径必须严格在上图中红色线的下面(不可以碰到图中的红线,可以碰到绿线)。则我们考虑任意一条不合法路径,例如下图中的橙线就是不合法的:
\qquad 经过上图所示的翻折后,原本的终点(6,6)变成了(5,7)。容易看出,任何不合法的路线均可以进行上图的翻折且终点为 (n-1,n+1) 。而且,从 (0,0) 到 (n-1,n+1) 的任意一条直线一定会过红线,且与一条非法的路线一一对应。这样一来,非法路线条数就是从(0,0)到(n-1,n+1)的总路线条数 C 2 n n − 1 C_{2n}^{n-1} C2nn−1。
\qquad 因此,这个题的答案就是 C 2 n n − C 2 n n − 1 C_{2n}^n-C_{2n}^{n-1} C2nn−C2nn−1.
\qquad 根据组合数的公式简单推导,便可得出其也等于 C 2 n n n + 1 \frac{C_{2n}^{n}}{n+1} n+1C2nn.
(
和
n
n
n 个 )
,问正确匹配的括号序列有多少种组合方式?看到输入样例为 3 且输出样例为 5 时一定要敏锐地意识到此题可能是在考察卡特兰数。
判断一个题是否是卡特兰数一般有两种方法:
- 符合经典题型:有 n n n 个 1 1 1 和 m m m 个 0 0 0 组成字符串,且在任意的前 k 个字符中,1 的个数不能少于 0 的个数。( n n n 和 m m m 不同时,这属于卡特兰数的变形)
- 满足递推关系: C n = ∑ i = 1 n C i − 1 ⋅ C n − i C_n=\sum\limits_{i=1}^nC_{i-1}\cdot C_{n-i} Cn=i=1∑nCi−1⋅Cn−i
\qquad 给定 n n n 个 0 0 0 和 n n n 个 1 1 1,它们将按照某种顺序排成长度为 2 n 2n 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0 0 0 的个数都不少于 1 1 1 的个数的序列有多少个。
\qquad 输出答案对 1 0 9 + 7 10^9+7 109+7 取模。
\qquad 共一行,包含整数 n n n。
\qquad 共一行,包含一个整数,表示答案。
\qquad 1 ≤ n ≤ 1 0 5 1 \le n\le 10^5 1≤n≤105
\qquad 此题只求一个数的组合数,直接用定义求即可。
#include <iostream> using namespace std; typedef long long LL; const int mod = 1e9 + 7; int qmi(int a) { int res = 1, k = mod - 2, p = mod; while (k) { if (k & 1) res = (LL)res * a % p; k >>= 1; a = (LL)a * a % p; } return res; } int main() { int n, res = 1; cin >> n; for (int i = (n << 1); i > n; i--) res = (LL)res * i % mod; for (int i = 2; i <= n; i++) res = (LL)res * qmi(i) % mod; res = (LL)res * qmi(n + 1) % mod; cout << res; return 0; }
\qquad 栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。
\qquad 栈有两种最重要的操作,即 p o p pop pop(从栈顶弹出一个元素)和 p u s h push push(将一个元素进栈)。
\qquad 栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。
\qquad 宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。
\qquad 宁宁考虑的是这样一个问题:一个操作数序列,从 1 , 2 , … , n 1,2,\dots,n 1,2,…,n,栈 A A A 的深度大于 n n n。
现在可以进行两种操作:
\qquad 使用这两种操作,由一个操作数序列就可以得到一系列的输出序列。
\qquad 你的程序将对给定的 n n n,计算并输出由操作数序列 1 , 2 , … , n 1,2,\dots ,n 1,2,…,n 经过操作可能得到的输出序列的总数。
\qquad 输入文件只含一个整数 n n n。
\qquad 输出文件只有一行,即可能输出序列的总数目。
\qquad 1 ≤ n ≤ 18 1\le n\le 18 1≤n≤18
\qquad
此题同样只求一个数的组合数,由于
n
n
n 很小,但又不能直接使用公式,因为
36
!
36!
36! 会爆 long long
;又因为
C
36
18
C_{36}^{18}
C3618 不超过
1
0
10
10^{10}
1010,开 long long
用递推法求组合数即可。
\qquad 此题甚至可以直接打表,下面给出两种代码。
1.递推:
#include <iostream> using namespace std; typedef long long LL; LL c[40][20]; int main() { int n; cin >> n; for (int i = 0; i <= (n << 1); i++) c[i][0] = 1; for (int i = 1; i <= (n << 1); i++) for (int j = 1; j <= min(i, n); j++) //相当于只计算杨辉三角的左半边 c[i][j] = c[i - 1][j] + c[i - 1][j - 1]; LL res = c[n + n][n]; cout << res / (n + 1); return 0; }
2.打表:
#include <iostream>
using namespace std;
int f[] = {0, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700};
int main() {
int n;
cin >> n;
cout << f[n];
return 0;
}
\qquad 由 n n n 个节点最多可组成多少个不同形态的二叉树?
\qquad 包含一个正整数 n n n。
\qquad 输出一个整数,表示不同形态的二叉树的个数。
\qquad 1 ≤ n ≤ 5000 1\le n\le 5000 1≤n≤5000
\qquad
设
f
(
n
)
f(n)
f(n) 表示
n
n
n 个节点可以构成的不同形态的二叉树数目,选择一个节点作为根节点,其左右子树节点数目为
i
−
1
i-1
i−1 和
n
−
i
n-i
n−i(
i
i
i范围是
[
1
,
n
]
[1,n]
[1,n]),因此根据乘法原理,这样的二叉树形态个数为
f
(
i
−
1
)
×
f
(
n
−
i
)
f(i-1) \times f(n-i)
f(i−1)×f(n−i),根据加法原理,有:
f
(
n
)
=
∑
i
=
1
n
f
(
i
−
1
)
×
f
(
n
−
i
)
,
f
(
0
)
=
1
f(n)=\sum\limits_{i=1}^{n}f(i-1)\times f(n-i),\;\;f(0)=1
f(n)=i=1∑nf(i−1)×f(n−i),f(0)=1
\qquad
其恰好为卡特兰数的递推关系式。
\qquad 此题不要求取模, n n n 又很大,所以需要使用高精度,以及质因数分解和约分。
#include <iostream> #include <cstring> #define MOD 10000 using namespace std; const int N = 1e5 + 5; int p[N], t; bool s[N]; int sum[N]; //sum[i]表示第i个质数p[i]出现的次数(指数) struct HP { //高精度 int l[4000]; int len; HP() { memset(l, 0, sizeof(l)); len = 0; } void print() { printf("%d", l[len]); for (int i = len - 1; i > 0; i--) { printf("%04d", l[i]); } printf("\n"); } }; HP operator*(const HP &a, const int &b) { //高精乘低精 HP c; c.len = a.len; int x = 0; for (int i = 1; i <= c.len; i++) { c.l[i] = a.l[i] * b + x; x = c.l[i] / MOD; c.l[i] %= MOD; } while (x) { c.len++; c.l[c.len] = x % MOD; x /= MOD; } return c; } void prime(int n) { //筛n以内的素数 int i, j; for (i = 2; i <= n; i++) { if (!s[i]) p[++t] = i; for (j = 1; p[j] <= n / i; j++) { s[i * p[j]] = 1; if (i % p[j] == 0) break; } } } int get(int n, int p) { //n!中p这个质数出现了几次 int res = 0; while (n) { res += n / p; n /= p; } return res; } int main() { int n; cin >> n; prime(n << 1); for (int i = 1; i <= t; i++) { int pr = p[i]; sum[i] = get(n << 1, pr) - 2 * get(n, pr); //相当于约分操作 } //需要额外对(n+1)进行质因数分解,再约分 int temp = n + 1, cnt; for (int i = 1; i <= t; i++) { cnt = 0; while (temp % p[i] == 0) { temp /= p[i]; cnt++; } sum[i] -= cnt; if (temp == 1) break; } HP res; res.l[1] = 1; res.len = 1; for (int i = 1; i <= t; i++) for (int j = 1; j <= sum[i]; j++) res = res * p[i]; //做乘法 res.print(); return 0; }
\qquad 给定一个整数 n n n,求以 1 , 2 , … , n 1,2,\dots ,n 1,2,…,n 为节点组成的二叉搜索树有多少种?
\qquad 结果对 1 0 9 + 7 10^9+7 109+7 取模后输出。
\qquad 共一行,包含一个整数 n n n。
\qquad 输出一个整数,表示对 1 0 9 + 7 10^9+7 109+7 取模后的结果。
\qquad 1 ≤ n ≤ 1000 1\le n\le 1000 1≤n≤1000
\qquad 二叉搜索树要满足:任意节点的值一定大于左子节点的,一定小于右子节点的。
\qquad 设函数 f ( n ) f(n) f(n) 表示以 n n n 个不同的数为节点能组成的搜索二叉树的总数。
\qquad
假设树根为
i
i
i,其左子树一定包含 1~i-1 的所有这
i
i
i 个数,其右子树 i+1~n 的所有这
n
−
i
n-i
n−i 个数。根据乘法原理,树根为
i
i
i 时的情况数为
f
(
i
−
1
)
×
f
(
n
−
i
)
f(i-1) \times f(n-i)
f(i−1)×f(n−i)。又因树根有
n
n
n 种情况,根据加法原理,有:
f
(
n
)
=
∑
i
=
1
n
f
(
i
−
1
)
×
f
(
n
−
i
)
,
f
(
0
)
=
1
f(n)=\sum\limits_{i=1}^{n}f(i-1)\times f(n-i),\;\;f(0)=1
f(n)=i=1∑nf(i−1)×f(n−i),f(0)=1
\qquad
此题代码与第一题代码完全一致,不过多赘述。
\qquad 暑假期间,小龙报名了一个模拟野外生存作战训练班。
\qquad 训练的第一个晚上,教官就给他们出了个难题。
\qquad 由于地上露营湿气重,必须选择在高处的树屋露营。
\qquad 小龙分配的树屋建立在一颗高度为 N + 1 N+1 N+1 尺的大树上,正当他发愁怎么爬上去的时候,发现旁边堆满了一些空心四方钢材(如图 1.1 1.1 1.1)。
\qquad 经过观察和测量,这些钢材截面的宽和高大小不一,但都是 1 1 1 尺的整数倍,教官命令队员们每人选取 N N N 个空心钢材来搭建一个总高度为 N N N 尺的阶梯来进入树屋,该阶梯每一步台阶的高度为 1 1 1 尺,宽度也为 1 1 1 尺。
\qquad 如果这些钢材有各种尺寸,且每种尺寸数量充足,那么小龙可以有多少种搭建方法?
\qquad 注:为了避免夜里踏空,钢材空心的一面绝对不可以向上。
\qquad 一个正整数 N N N,表示阶梯的高度。
\qquad 一个正整数,表示搭建方法的个数。
\qquad 注:搭建方法个数可能很大。
\qquad 1 ≤ N ≤ 500 1\le N\le 500 1≤N≤500
3
5
5 5 5 种搭建方法如图 1.2 1.2 1.2 所示:
\qquad 由题可知:要用 n n n 个钢材搭 n n n 级台阶,显然每级台阶都是一个钢材。
\qquad 一定存在以一个钢材(第 i i i 个)占据了最左下角的区域(下图红色),且这个钢材可以将所有其他钢材分为上下两部分,即它上面要搭 n − i n-i n−i 个钢材的台阶(下图绿框),下面要搭 i − 1 i-1 i−1 个钢材的阶梯(下图蓝框)。
\qquad
而
i
i
i 的取值是 1~n ,故满足递推关系:
C
n
=
∑
i
=
1
n
C
i
−
1
⋅
C
n
−
i
C_n=\sum\limits_{i=1}^nC_{i-1}\cdot C_{n-i}
Cn=i=1∑nCi−1⋅Cn−i。此题考查卡特兰数。
\qquad 我们称一个长度为 2 n 2n 2n 的数列是有趣的,当且仅当该数列满足以下三个条件:
它是从 1 ∼ 2 n 1 \sim 2n 1∼2n 共 2 n 2n 2n 个整数的一个排列 { a n } n = 1 2 n \{a_n\}_{n=1}^{2n} {an}n=12n;
所有的奇数项满足 a 1 < a 3 < ⋯ < a 2 n − 1 a_1<a_3< \dots < a_{2n-1} a1<a3<⋯<a2n−1,所有的偶数项满足 a 2 < a 4 < ⋯ < a 2 n a_2<a_4< \dots <a_{2n} a2<a4<⋯<a2n;
任意相邻的两项 a 2 i − 1 a_{2i-1} a2i−1 与 a 2 i a_{2i} a2i 满足: a 2 i − 1 < a 2 i a_{2i-1}<a_{2i} a2i−1<a2i。
\qquad
对于给定的
n
n
n,请求出有多少个不同的长度为
2
n
2n
2n 的有趣的数列。
因为最后的答案可能很大,所以只要求输出答案对
p
p
p 取模。
\qquad 一行两个正整数 n , p n,p n,p。
\qquad 输出一行一个整数表示答案。
样例输入 #1
3 10
样例输出 #1
5
【数据范围】 对于 50 % 50\% 50% 的数据, 1 ≤ n ≤ 1000 1\le n \le 1000 1≤n≤1000; 对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 6 1\le n \le 10^6 1≤n≤106, 1 ≤ p ≤ 1 0 9 1\le p \le 10^9 1≤p≤109。
【样例解释】 对应的5个有趣的数列分别为(1,2,3,4,5,6),(1,2,3,5,4,6),(1,3,2,4,5,6),(1,3,2,5,4,6),(1,4,2,5,3,6)。
\qquad 从1到 2 n 2n 2n 依次考察每个元素放置的位置,1 只能放在第一个位置,2 只能放在第二个位置,且任意时刻我们放置的数据中奇数项的个数必须大于等于偶数项的数量。否则,假设我们奇数项放置2个元素,偶数项放置3个元素,则不合法,如下图:
\qquad 我们可以这样对应:在从1到 2n 依次考察每个元素时,如果这个数据放到奇数位置,标为0,否则标为1。则任意前缀中0的个数要大于等于1的个数。
\qquad 因此此题考查卡特兰数。
\qquad 由于此题 p p p 不固定,不可求逆元,只能使用分解质因数再约分的方法求组合数。
#include <iostream> using namespace std; typedef long long LL; const int N = 2e6 + 5; //注意要大于10^5的两倍 int pr[N], cnt; bool s[N]; int sum[N]; void prime(int n) { s[0] = s[1] = 1; for (int i = 2; i <= n; i++) { if (!s[i]) pr[++cnt] = i; for (int j = 1; pr[j] <= n / i; j++) { s[i * pr[j]] = 1; if (i % pr[j] == 0) break; } } } int get(int n, int p) { int res = 0; while (n) { res += n / p; n /= p; } return res; } int main() { int n, mod; cin >> n >> mod; prime(n << 1); for (int i = 1; i <= cnt; i++) { int p = pr[i]; sum[i] = get(n << 1, p) - 2 * get(n, p); } int t = n + 1; for (int i = 1; i <= cnt; i++) { int p = pr[i]; while (t % p == 0) { sum[i]--; t /= p; } if (t == 1) break; } int res = 1; for (int i = 1; i <= cnt; i++) for (int j = 1; j <= sum[i]; j++) res = (LL)res * pr[i] % mod; cout << res; return 0; }
\qquad 圆上有 2 N 2N 2N 个不同的点,小 x 想用 N 条线段把这些点连接起来(每个点只能连一条线段), 使所有的线段都不相交,他想知道这样的连接方案有多少种?
\qquad 我们规定 start 为起始点,顺时针方向为正方向。
\qquad 从 start 开始,将一条弦 最先遇到的那个端点标记为 +,后来遇见的另一个端点为标记为 −,就可以得到如下图。
\qquad 对于标蓝的点来说,我们可以看出,在它之前,+ 号的标记是必须大于等于 - 号的标记的。
参考资料:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。