卡特兰数求解方法:
通项公式: C n = ( 2 n n ) / ( n + 1 ) C_n=\binom{2n}{n} / (n+1) Cn=(n2n)/(n+1)
递推公式: C 1 = 1 , C n = 4 n − 2 n + 1 C n − 1 C_1=1,C_n=\frac{4n-2}{n+1}C_{n-1} C1=1,Cn=n+14n−2Cn−1。
另一个递推公式:
C
0
=
1
C_0=1
C0=1
C
n
=
∑
i
=
0
n
−
1
C
i
C
n
−
i
−
1
C_n=\displaystyle\sum\limits_{i=0}^{n-1} C_iC_{n-i-1}
Cn=i=0∑n−1CiCn−i−1
CSES2064 - Bracket Sequences I
经典的卡特兰数。有多少组括号,答案就是卡特兰数的第几个数。
卡特兰数通项公式: C n = ( 2 n n ) / ( n + 1 ) C_n=\binom{2n}{n} / (n+1) Cn=(n2n)/(n+1)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
LL jc[2000050], inv[2000050];
template <class T> T power(T a, LL b) {
T res = 1;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
void init(LL iN) {
jc[0] = 1;
for (LL i = 1; i <= iN + 1; ++i) {
jc[i] = (jc[i - 1] * i) % mod;
}
inv[iN + 1] = power(jc[iN + 1], mod - 2);
for (LL i = iN; i >= 0; --i) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
LL C(LL N, LL M) {
return jc[N] * inv[M] % mod * inv[N - M] % mod;
}
void main2() {
LL n;
cin >> n;
if (n % 2 == 1) {
cout << 0;
return;
}
cout << C(n, n / 2) * power(n / 2 + 1, mod - 2) % mod;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
init(1000000);
// cin >> _;
while (_--) main2();
return 0;
}
HDU2067 - 小兔的棋盘
题面是非常经典的卡特兰数模型。
可以认为向下走是+1,向右走是-1,然后走到对角线上意味着前缀和为 0 0 0,控制前缀和不能小于 0 0 0,和不能跨过对角线是等价的。这样可以在对角线的下侧走。
可以认为向右走是+1,向下走是-1,然后走到对角线上意味着前缀和为 0 0 0,控制前缀和不能小于 0 0 0,和不能跨过对角线是等价的。这样可以在对角线的上侧走。
这样两次走法都是合法的,每一种走法都是卡特兰数的第 2 n 2n 2n项。
但是问题来到了求解时越界的问题。如果我们采用第一个递推公式: C 1 = 1 , C n = 4 n − 2 n + 1 C n − 1 C_1=1,C_n=\frac{4n-2}{n+1}C_{n-1} C1=1,Cn=n+14n−2Cn−1,此时会爆掉long long。可以用__int128来解决,或者用第二种递推公式: C 0 = 1 , C n = ∑ i = 0 n − 1 C i C n − i − 1 C_0=1,C_n=\displaystyle\sum\limits_{i=0}^{n-1} C_iC_{n-i-1} C0=1,Cn=i=0∑n−1CiCn−i−1,在这道题目中是不会越界的。
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long LL;
const LL mod = 1e9 + 7;
LL c[80];
void main2() {
int n, cnt = 0;
while (1) {
cin >> n;
if (n == -1) return;
++cnt;
cout << cnt << ' ' << n << ' ' << c[n] * 2 << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
c[0] = 1;
for (int i = 1; i <= 70; ++i) {
for (int j = 0; j < i; ++j) {
c[i] += (c[j] * c[i - 1 - j]);
}
}
// cin >> _;
while (_--) main2();
return 0;
}
CSES2187 - Bracket Sequences II
有
n
n
n对括号的合法序列个数是
(
2
n
n
)
n
+
1
\frac{\binom{2n}{n}}{n+1}
n+1(n2n),长度为
2
n
2n
2n的括号序列总个数是
(
2
n
n
)
\binom{2n}{n}
(n2n),所以非法括号序列的个数就是:
(
2
n
n
)
−
(
2
n
n
)
n
+
1
=
(
2
n
n
)
×
n
n
+
1
=
(
2
n
)
!
n
!
×
n
!
×
n
n
+
1
=
(
2
n
)
!
(
n
−
1
)
!
(
n
+
1
)
!
=
(
2
n
n
+
1
)
\binom{2n}{n}-\frac{\binom{2n}{n}}{n+1}=\binom{2n}{n} \times \frac{n}{n+1}=\frac{(2n)!}{n!\times n!}\times \frac{n}{n+1}=\frac{(2n)!}{(n-1)!(n+1)!}=\binom{2n}{n+1}
(n2n)−n+1(n2n)=(n2n)×n+1n=n!×n!(2n)!×n+1n=(n−1)!(n+1)!(2n)!=(n+12n)
在小球放盒子的一些公式推导中,我们用到了这样的思想,即当想把公式从不允许空推到允许空的时候,可以给每一个盒子虚空放一个不存在的小球,然后带上这些小球,按照不允许空的公式算一个方案数,得到的结果就是答案。
这个题中,我们假设题目所给的前缀串中左括号个数为 x x x,右括号个数为 y y y,那么不难看出,还剩下 n − x n-x n−x个左括号和 n − y n-y n−y个右括号待填。这些可以演变成 n − x n-x n−x对括号和 x − y x-y x−y个右括号待填。我们想求填完这些后整个串合法的填充方案数,可以转化成总共的方案数-非法方案数。
当我们只考虑 n − x n-x n−x对括号的时候,不合法的方案数个数是 ( 2 ( n − x ) ( n − x ) + 1 ) \binom{2(n-x)}{(n-x)+1} ((n−x)+12(n−x)),然后按照不允许空到允许空的思想,我们现在还剩下 x − y x-y x−y个右括号等待填充,可以认为剩下的 x − y x-y x−y个空位就是空盒子, x − y x-y x−y个右括号就是虚构出来的小球,那么非法方案数就是 ( 2 ( n − x ) + x − y 2 ( n − x ) + 1 + x − y ) = ( 2 n − x − y n − y + 1 ) \binom{2(n-x)+x-y}{2(n-x)+1+x-y}=\binom{2n-x-y}{n-y+1} (2(n−x)+1+x−y2(n−x)+x−y)=(n−y+12n−x−y)。
最后的答案 a n s = ( 2 n − x − y n − x ) − ( 2 n − x − y n − y + 1 ) ans=\binom{2n-x-y}{n-x}-\binom{2n-x-y}{n-y+1} ans=(n−x2n−x−y)−(n−y+12n−x−y)。
注意要特判给定的前缀不合法和长度不合法的情况。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
LL jc[2000050], inv[2000050];
template <class T> T power(T a, LL b) {
T res = 1;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
void init(LL iN) {
jc[0] = 1;
for (LL i = 1; i <= iN + 1; ++i) {
jc[i] = (jc[i - 1] * i) % mod;
}
inv[iN + 1] = power(jc[iN + 1], mod - 2);
for (LL i = iN; i >= 0; --i) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
LL C(LL N, LL M) {
return jc[N] * inv[M] % mod * inv[N - M] % mod;
}
LL n, x = 0, y = 0;
string s;
void main2() {
cin >> n >> s;
if (n % 2 == 1) {
cout << 0;
return;
}
for (char c: s) {
if (c == '(') ++x;
else ++y;
if (x < y) {
cout << 0;
return;
}
}
if (y > x) {
cout << 0;
return;
}
if (x > n / 2) {
cout << 0;
return;
}
n >>= 1;
cout << ((C(n * 2 - x - y, n - x) - C(n * 2 - x - y, n - x - 1)) % mod + mod) % mod;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
init(1000000);
while (_--) main2();
return 0;
}
HDU5184 - Brackets
和上一道题一模一样,只不过改成了多组读入。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
LL jc[2000050], inv[2000050];
template <class T> T power(T a, LL b) {
T res = 1;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
void init(LL iN) {
jc[0] = 1;
for (LL i = 1; i <= iN + 1; ++i) {
jc[i] = (jc[i - 1] * i) % mod;
}
inv[iN + 1] = power(jc[iN + 1], mod - 2);
for (LL i = iN; i >= 0; --i) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
LL C(LL N, LL M) {
return jc[N] * inv[M] % mod * inv[N - M] % mod;
}
LL n, x, y;
string s;
void main2() {
cin >> s;
if (n % 2 == 1) {
cout << 0 << '\n';
return;
}
x = y = 0;
for (char c: s) {
if (c == '(') ++x;
else ++y;
if (x < y) {
cout << 0 << '\n';
return;
}
}
if (x > n / 2) {
cout << 0 << '\n';
return;
}
n >>= 1;
cout << ((C(n * 2 - x - y, n - x) - C(n * 2 - x - y, n - x - 1)) % mod + mod) % mod << '\n';
}
int main() {
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
init(1000000);
while (cin >> n) {
main2();
}
return 0;
}