《卡特兰数》 学习总结
貌似不是很有用的一个东西。
这是我觉得讲的最好的一篇文章
定义
这个东西光定义就有好几种,并且每种定义都有一定的运用。
-
递归定义:
f_n=\sum\limits_{i=0}^{n-1} f_i\times f_{n-i-1} -
递推关系:
f_n=f_{n-1}\times \frac {(4n-2)}{n+1} -
通项公式:
\frac 1{n+1}C_{2n}^n=C_{2n}^n-C_{2n}^{n+1}
递推关系怎么来的不太知道,从上面化简应该可以得到,不过不需要特别注意就是了。
递推定义解释
至于递归定义是怎么来的
因为
大概长成上面这个样子。
通项公式解释
考虑一个关于卡特兰数最基本的问题,我们现在要从
如果直接正着求,发现妃常难求,考虑求他的补集,求非法的走法的方案。
那么我们定义第一次碰到直线的位置为
那么我们考虑从这个位置之后的路径我们都沿着
考虑黑色为原本的路径,橙色为翻折过后的路径,这样实际上我们发现只要经过翻折之后,路径的终点一定都是
因为向右的边还剩
然后我们有结论,从
相当于每条路径都是一一映射过去的。
所有总方案数就是
一个小小的拓展,如果要去
同理,我们终点是
方案就是
应用
括号匹配
合法括号匹配串的数量,恰好
实际上将左括号看做向右,右括号看做向上同理。
然后一样做掉就可以了。
不相交弦问题
这里使用了
我们将最上面的点看成起点,将逆时针方向看做正方向。
将匹配的起点看做左括号,终点看做右括号,容易发现所有前缀和中左括号的数量大于等于右括号的数量。
所以又是卡特兰数问题了。
二叉树个数
有
容易得到
容易发现这和卡特兰数的递归定义一样,所以实际上这个东西就是卡特兰数。
凸多边形三角划分问题
和上面不相交弦感觉貌似很像,其实。
我们在凸多边形中随便挑两个顶点连一条边,这个凸多边形就会被分成两个小凸多边形,由于每条直线不能相交,接下来我们就只要求这两个小凸多边形的划分方案然后乘起来即可
和二叉树的构成问题一样,我们枚举大凸多边形被分成的两个小凸多边形的大小即可
实战演练
P1375 小猫
容易发现这是不相交弦问题,直接上卡特兰数就行了。
P1754 球迷购票问题
典中典,相当于任意时刻
不过这里不能用组合数,会爆掉,只能用递推公式。
P3200 [HNOI2009] 有趣的数列
随便口胡一下。
容易发现对于一个
所以填在
然后考虑转换题意,考虑从
有一个结论,任意时刻,奇数数量大于等于偶数数量。
然后这个就是卡特兰数没什么好说的了。
不过模数不一定是质数要对所有数进行分解质因数,这里有一个
for(int i=n*2;i>1;--i) {
if(mins[i]<i) {
sum[mins[i]]+=sum[i];
sum[i/mins[i]]+=sum[i];
}
}
其中
上面的说完了,还有一个很严重的问题,结论怎么证明。
具体证明如果存在某一时刻偶数个数大于奇数,那么我们考虑此事填了
那么我们考虑偶数的下一个位置可以填什么,如果偶数想放在
然后对于其他情况都会发现严格满足题目限制(其实就是我菜,不会证),于是证毕。
P7118 Galgame
首先可以直接统计的是长度小于我们当前序列的,对于大小为某一个值的二叉树的个数就是卡特兰数。
接着对于每个节点,考虑它的左节点,即另一棵树的左节点的数量一定小于当前树最节点数量就可以统计答案。
具体的,统计一下,枚举左子树的大小。
for(int i=0;i<sz[l[u]];++i) {
(ans+=xs*ctl[i]%P*ctl[sz[u]-1-i]%P)%=P;
}
然后考虑大小相等的情况,要么往左边递归(左边又不痛的地方),要么往右边递归(说明左边完全相同,不同的地方在右边),所以递归左边的时候要再乘上右边可能的方案数,即
但是这样复杂度是
有一个比较厉害的优化。
我们不仅可以枚举左子树,也可以枚举右子树减去不合法的,具体的,用全集减去一定不合法的。
然后我们在做的时候选择小的一边做就可以了。
这样就相当于启发式合并,时间复杂度
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN=2e6+10,P=998244353;
int n;
int l[MAXN],r[MAXN];
LL pw[MAXN],inv[MAXN],ctl[MAXN];
LL C(LL x,LL y) {
if(x<y) return 0;
return pw[x]*inv[y]%P*inv[x-y]%P;
}
int sz[MAXN];
LL ans;
void dfs(int u) {
sz[u]=1;
if(l[u]) dfs(l[u]);
if(r[u]) dfs(r[u]);
sz[u]+=sz[l[u]]+sz[r[u]];
}
void dfs1(int u,LL xs) {
if(sz[l[u]]<=sz[r[u]]+1) {
for(int i=0;i<sz[l[u]];++i) {
(ans+=xs*ctl[i]%P*ctl[sz[u]-1-i]%P)%=P;
}
}
else {
ans=(ans+ctl[sz[u]]*xs%P)%P;
for(int i=0;i<=sz[r[u]];++i) {
ans=(ans-xs*ctl[i]%P*ctl[sz[u]-1-i]%P+P)%P;
}
}
if(l[u]) dfs1(l[u],xs*ctl[sz[r[u]]]%P);
if(r[u]) dfs1(r[u],xs);
}
int main () {
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d%d",&l[i],&r[i]);
}
pw[0]=inv[0]=inv[1]=1;
for(int i=1;i<=(n<<1);++i) {
pw[i]=pw[i-1]*i%P;
}
for(int i=2;i<=(n<<1);++i) {
inv[i]=(P-(P/i))*inv[P%i]%P;
}
for(int i=2;i<=(n<<1);++i) inv[i]=inv[i-1]*inv[i]%P;
ctl[0]=1;
for(int i=1;i<=n;++i) {
ctl[i]=(C(i*2,i)-C(i*2,i-1)+P)%P;
}
for(int i=1;i<n;++i) {
ans+=ctl[i];
} ans%=P;
dfs(1);
dfs1(1,1);
printf("%lld\n",ans);
return 0;
}
P3978 [TJOI2015] 概率论
记
那么有结论 打表呀
然后答案就是
具体如何证明。
对于大小为
然后同时可以保证一定不会重复,所有就是
然后就证明完了。
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN=110;
double n;
double f[MAXN],ctl[MAXN];
int main () {
scanf("%lf",&n);
printf("%.12lf",(double)n*(n+1)/(2*(2*n-1)));
// ctl[0]=ctl[1]=1;
// for(int i=2;i<=n;++i) {
// ctl[i]=ctl[i-1]*(4*i-2)/(i+1);
// }
// f[1]=1;
// for(int i=2;i<=n;++i) {
// for(int j=0;j<i;++j) {
// f[i]=f[i]+(2*f[j]*ctl[i-1-j]*ctl[j]);
// }
// f[i]=f[i]*1.0/ctl[i];
// }
// printf("%.9lf",f[n]);
return 0;
}
P5014 水の三角(修改版)
容易发现,实际上可以枚举向右走的数量,然后转化成一个卡特兰数和插板法就好了,比较显然。
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN=2e6+10,P=998244353;
LL ksm(LL x,LL y) {
LL ret=1;
while(y) {
if(y&1) ret=ret*x%P;
x=x*x%P;
y>>=1;
}
return ret;
}
LL n;
LL pw[MAXN],inv[MAXN];
LL C(LL x,LL y) {
if(x<y) return 0;
return pw[x]*inv[x-y]%P*inv[y]%P;
}
LL ctl(LL n,LL m) {
return (C(n+m,n)-C(n+m,n+1)+P);
}
void vmain() {
scanf("%lld",&n);
LL a=sqrt(n)-1,k;
while(n-(LL)a*(a-1)/2>a) ++a;
k=n-(LL)a*(a-1)/2;
LL ans=0;
for(int i=0;i<k;++i) {
LL _a=a-1-i,_k=k-1-i;
ans+=C(_a+_k+i,i)*ctl(_a,_k)%P;
if(ans>=P) ans-=P;
}
printf("%lld\n",ans);
}
int main () {
pw[0]=inv[0]=1;
for(int i=1;i<=2e6;++i) {
pw[i]=pw[i-1]*i%P;
inv[i]=ksm(pw[i],P-2);
}
int T;
scanf("%d",&T);
while(T--) vmain();
return 0;
}
咕掉了,不会做的题P4769 [NOI2018] 冒泡排序
总结:
这个东西其实就是你得证明你求的东西是