跳到主要内容

AcWing 889. 满足条件的 01 序列

本节目标

用卡特兰数统计任意前缀中 0 的数量均不少于 1 的数量的 01 序列。

题意与约束

长度为 2n 的序列含 n0n1,且任意前缀中的 0 数量不少于 1 数量,输出这样的序列数模 1_000_000_007

第一反应与瓶颈

枚举所有 C(2n,n) 个位置选择,再逐个检查前缀,会随 n 指数式增长,无法处理题目上界。

数学关系与算法推导

0 看作向右一步、1 看作向上一步。全部从 (0,0)(n,n) 的路径有 C(2n,n) 条;第一次走到对角线错误一侧的路径经反射对应 C(2n,n-1) 条,因此合法数为两者之差。

正确性依据

每条非法路径都有唯一的第一次越界位置,反射越界前的路径段可逆地映射到终点偏移一格的路径;故非法与 C(2n,n-1) 一一对应。相减后既不遗漏也不重复。

样例执行过程

n=3 时全部路径为 C(6,3)=20,非法路径为 C(6,2)=15,答案为 5。测试还将 n≤8 的结果与暴力前缀枚举对拍。

代码实现

C++17
#include <iostream>
using namespace std;

constexpr long long MOD = 1000000007;

long long powerModulo(long long base, long long exponent) {
long long result = 1;
while (exponent > 0) {
if ((exponent & 1) != 0) {
result = result * base % MOD;
}
base = base * base % MOD;
exponent >>= 1;
}
return result;
}

long long countValidBinarySequences(int n) {
long long numerator = 1;
long long denominator = 1;
for (int value = 1; value <= n; value++) {
numerator = numerator * (n + value) % MOD;
denominator = denominator * value % MOD;
}
long long chooseMiddle = numerator * powerModulo(denominator, MOD - 2) % MOD;
long long choosePrevious = n == 0 ? 0 : chooseMiddle * n % MOD * powerModulo(n + 1, MOD - 2) % MOD;
return (chooseMiddle - choosePrevious + MOD) % MOD;
}

#ifndef ALGORITHM_TUTORIAL_NO_MAIN
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);

int n;
cin >> n;
cout << countValidBinarySequences(n) << '\n';
return 0;
}
#endif

复杂度分析

连乘计算组合数分子、分母各需 O(n),求逆元需 O(log MOD),额外空间 O(1)

边界与易错点

n=1 的答案是 1。模意义下做减法要加上 MOD 再取模,避免得到负数;分母的逆元依赖质数模数且分母非零。

模式迁移

“任意前缀不能跌破边界”的计数常可尝试路径映射和反射思想;这里不扩展到更复杂的容斥体系。回到组合计数