【做题笔记】卡特兰数

卡特兰数求解方法:

通项公式: 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+14n2Cn1

另一个递推公式:
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=0n1CiCni1

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+14n2Cn1,此时会爆掉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=0n1CiCni1,在这道题目中是不会越界的。

#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=(n1)!(n+1)!(2n)!=(n+12n)

在小球放盒子的一些公式推导中,我们用到了这样的思想,即当想把公式从不允许空推到允许空的时候,可以给每一个盒子虚空放一个不存在的小球,然后带上这些小球,按照不允许空的公式算一个方案数,得到的结果就是答案。

这个题中,我们假设题目所给的前缀串中左括号个数为 x x x,右括号个数为 y y y,那么不难看出,还剩下 n − x n-x nx个左括号和 n − y n-y ny个右括号待填。这些可以演变成 n − x n-x nx对括号和 x − y x-y xy个右括号待填。我们想求填完这些后整个串合法的填充方案数,可以转化成总共的方案数-非法方案数。

当我们只考虑 n − x n-x nx对括号的时候,不合法的方案数个数是 ( 2 ( n − x ) ( n − x ) + 1 ) \binom{2(n-x)}{(n-x)+1} ((nx)+12(nx)),然后按照不允许空到允许空的思想,我们现在还剩下 x − y x-y xy个右括号等待填充,可以认为剩下的 x − y x-y xy个空位就是空盒子, x − y x-y xy个右括号就是虚构出来的小球,那么非法方案数就是 ( 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(nx)+1+xy2(nx)+xy)=(ny+12nxy)

最后的答案 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=(nx2nxy)(ny+12nxy)

注意要特判给定的前缀不合法和长度不合法的情况。

#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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值