Catalan数
概要
\(Catalan\)数的递推式:
\(Catalan\)数的递推解(通项式):
\(Catalan\)数的前五项:\(1,1,2,5,14\)。
\(Catalan\)数在\(OEIS\)上:A000108
应用
没有什么用的东西说完了,我们来讨论它的应用:
合法路径:从\((0,0)\)到\((n,n)\),只能向右或向上且不能越过\(y=x\),求路径数。
证明:不考虑不越过\(y=x\)的限制,方案数为\(\begin{pmatrix} 2n \\ n \\ \end{pmatrix}\),越过\(y=x\)的方案数等价于\((-1,1)\)到\((n,n)\)的方案数(路径沿\(y=x+1\)对称),为\(\begin{pmatrix} 2n \\ n-1 \\ \end{pmatrix}\),与\((4)\)式相同。
合法括号序列:\(n\)个左括号,\(n\)个右括号,求组成的合法括号序列。原题
与合法路径等价。
出栈序列:进栈序为\(1,2,\dots,n\),求出栈序计数。原题
进栈为左括号,出栈为右括号,即与合法括号序列等价。
二叉树计数:给定\(n\)个节点,求不同的二叉树种数。
左儿子为左括号,右儿子为右括号,考虑\(dfs\)序,与合法括号等价。
凸多边形划分:将一个凸多边形连接若干不相交的对角线划分的方案数。\((C_{n-2})\)
容易看出递推形式与\((1)\)相同。
扩展\(Catalan\)数:\(n\)位\(01\)串,\(m\)个\(0\),前缀\(1\)的个数不少于\(0\),求计数。原题
和合法路径的证明方式基本一致,即由\((0,0)\)到\((n-m, m)\)的路径数减去\((-1,1)\)到\((n-m, m)\)的路径数。
例题
然后就是一些很裸的例题。
答案是\(Catalan\)数,可以由手推前四项基本得出。
如何推导?对于大小为\(n\)的阶梯,在上面切掉一个大小为\(i(i\in [0,n]\)的阶梯,在右边切掉一个大小为\(n-i+1\)的阶梯,中间放一个矩形,一共为\(i+(n-i+1)+1=n\)个矩形。不难发现这是\((1)\)式。
可是我们需要高精度,我不想写高精怎么办?
于是就有了下面这份代码:
n=int(input())
ans=1
for i in range(n+2, n*2+1):
ans*=i
for i in range(1, n+1):
ans//=i
print(ans)
\(python\)大法好!
这个题应该是最有趣的一个。
首先打表(或手推)找规律肯定是一种省时省力的方法。
考虑推导:把\(1-2n\)依次放入数列,每次放在奇数位或偶数位的最前面一个。任何时候奇数位个数不少于偶数位,等价于合法括号。
但这个题还有一个问题:模数不为质数。这意味着我们不能常规地求逆元得到答案。
考虑质因数分解,因为\(C_n=\frac{\prod_{i=n+2}^{2n}}{\prod_{i=1}^{n}}\),于是可以这么写。
//线筛b[i]
rep(i, 1, n) cnt[i]=-1;//分母
rep(i, n+2, n<<1) cnt[i]=1;//分子
per(i, n<<1, 2) if (b[i]<i) //b[i]为i的一个质因数
cnt[b[i]]+=cnt[i], cnt[i/b[i]]+=cnt[i];
rep(i, 2, n<<1) if (b[i]==i) ans=1ll*ans*Pow(i, cnt[i])%P;
复杂度是什么?预处理线筛\(O(n)\),分解\(O(n)\),统计答案\(O(\pi (n)\cdot log(n))\approx O(\frac{n}{ln(n)}\cdot log(n))\approx O(n)\),所以总复杂度为\(O(n)\)
完整代码:
#include<cstdio>
#define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
#define per(i, a, b) for (register int i=(a); i>=(b); --i)
using namespace std;
const int N=2000005;
int p[N], b[N], cnt[N], n, P, ans=1, tot;
inline int read()
{
int x=0,f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
int Pow(int x, int t)
{
int res=1;
for (; t; t>>=1, x=1ll*x*x%P) if (t&1) res=1ll*res*x%P;
return res;
}
int main()
{
n=read(); P=read();
rep(i, 2, n<<1)
{
if (!b[i]) p[++tot]=i, b[i]=i;
for (int j=1; j<=tot && p[j]*i<=n<<1; j++)
{
b[p[j]*i]=p[j];
if (!(i%p[j])) break;
}
}
rep(i, 1, n) cnt[i]=-1;
rep(i, n+2, n<<1) cnt[i]=1;
per(i, n<<1, 2) if (b[i]<i)
cnt[b[i]]+=cnt[i], cnt[i/b[i]]+=cnt[i];
rep(i, 2, n<<1) if (b[i]==i) ans=1ll*ans*Pow(i, cnt[i])%P;
printf("%d\n", ans);
return 0;
}
部分摘自百度百科:卡特兰数