卡特兰数 折线法浅析
这里以合法括号匹配为例研究 Catalan数
这篇是在模拟赛中总结的关于 研究 恰好 m 个失配的方案数 m=0 的时候 就是我们平时了解到的卡特兰数 合法括号匹配数
那么考虑 m为任意数字的 方法 显然 我们需要知道卡特兰数的证明方法
首先证明方法有折线法 由折线法 我们不妨引出 另外一种证明思想 我思故我在 嘛
卡特兰数对应的 问题模型都是 在第K次执行 操作2的时候 操作1都是至少执行了K次 那么我们用x轴表示 当前的操作次数 一共需要2n次
然后把操作1当做 向上45走根号2步 操作2当成 向下45走根号2步 那么此时我们就能发现一条到达 (2n,0) 的折线
那么合法的方案数我们也能发现也就是 这个折线没有任意时刻 跨过x轴 那么我们考虑这个时候 用所有的方案数 减去 不合法的方案数
此时合法括号序列恰好对应我们寻找合法路径的方案数 那么所有的方案数对应$\binom{2n}{n}$
那么此时来考虑 怎么求所有不合法的方案数 我们记得 我们在求从(0,0)出发 向上 或者 向右 到达(n,n)
不跨过y=x 的方案数 我们是怎么求证的呢 我们画个图来探讨一下 我真不想画 截yxc神仙的b站上的 组合数学讲解 推荐他的文章 很详细 不太清楚组合数的同学 可以看一下他的视频
现在任意存在一个 不合法 的路径 那么我们找到第一次 跨过 这个直线的部分 到达另外一条直线 也就是图上 绿色 部分
我们此时将从这个点到后面的路径全部关于这个直线翻折过去
那么此时终点变成了 (n-1 n+1)那么所有不合法的路径就对应 所有从(0,0) 到达(n-1,n+1) 所有路径条数 那么方案数我们就显然知道了
此时我们回到上面的折线法 我们考虑 每次45度 这种方法 也是把第一次 跨过x轴 到达y = -1 这个直线的时候 K 之后 K+1~2n 之间的路径 全部关于 y = -1 对称过来
不过是把 y=x 这个基准线 变成x轴 然后把绿色的线 变成y = -1 感觉这种思想很巧妙吧 把难以统计的答案变成容易统计的方案数
那么 终点变成了 (2n,-2) 此时操作1 比操作 少2 那么操作1 有n-1 操作2 有n+1 那么考虑 此时 就是2n 中选择 n-1 种的方案数 $\binom{2n}{n-1}$
那么不合法的方案数 也就是 至少一次不匹配的方案数 此时卡特兰数的 我们就证明了出来 有必要把这种翻折 寻找基准线的方法 理解一下。
此时$\binom{2n}{n}-\binom{2n}{n-1}$ 就是方案数 其实从这个时候 我们不妨思考一下 我们减去的就是对应的 至少一种不匹配的方案数
那么 我们考虑 如果是恰好m次不合法的方案数 那么 我们 可以转化成 至少m次不匹配的方案数 - 至少m+1次不匹配的方案数 其实对应到折线图上
我们此时把基准线变成y=-m 这个时候 我们求出来 按照 上述的方案数 此时 我们求出来 就是 至少m次 不合法的方案数
此时 cat(n,m)-cat(n,m+1)就是答案了 并且 cat(n,m)=$\binom{2n}{n}-\binom{2n}{n-m-1} $ 带入即可
#include<bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &x) { x=0;T f=1,ch=getchar(); while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x*=f; } const ll mod=998244353; ll fac[2002002],inv[2002002],n,m,ans; inline ll pow(ll a,ll b) { ll res=1; while(b) { if(b&1) res=res*a%mod; a=a*a%mod; b>>=1; } return res; } inline ll C(ll n,ll m) { if(m>n) return 0; if(m<0) return 0; if(n<0) return 0; return fac[n]*inv[m]%mod*inv[n-m]%mod; } inline ll c(ll n) {return (C(2ll*n,n)-C(2ll*n,n+1)+mod)%mod;} ll exctl(int n,int m) {return (C(2ll*n,n)-C(2*n,n-m-1)+mod)%mod;} int main() { freopen("excatalan.in","r",stdin); freopen("excatalan.out","w",stdout); read(n); read(m); fac[0]=1; for(int i=1;i<=2*n;i++) { fac[i]=fac[i-1]*i; fac[i]%=mod; } inv[2*n]=pow(fac[2*n],mod-2); for(int i=2*n-1;i>=0;i--) { inv[i]=inv[i+1]*(i+1); inv[i]%=mod; } if(m==0) ans=c(n); else ans=(exctl(n,m)-exctl(n,m-1)+mod)%mod; printf("%lld\n",ans); return 0; }