Catalan Number & Lucas定理 & 中国剩余定理(CRT)

又双叒叕来水数论了
今天来学习\(Lucas \:\ \& \:\ Catalan Number\)
二者有着密切的联系(固然还有CRT),因此放在一块儿学习一下ios

Catalan Number

定义

卡特兰数(Catalan Number)又称卡塔兰数,是组合数学中一个常常出如今各类计数问题中的数列
\(1,2,5,14,42,132,429,1430,4862,16796,58786,208010,742900\)
啊这
其实跟\(Lucas\)没有什么太大的联系
只有其中一个求\(Catalan Number\)的公式可能会用到\(Lucas\)定理算法

性质

上面提到说其中一个公式,也就是说求\(Catalan Number\)有不少方法数组

  • 递归公式1

\[f(n) = \sum_{i=0}^{n-1}f(i)*f(n-i-1) \]

  • 递归公式2

\[f(n)=\frac {f(n-1)*(4*n-2)} {n+1} \]

  • 组合公式1(就是这个的分子可能会用到\(Lucas\)突然找不到手头作过的那道题了)固然这个式子也是它的通项公式

\[f(n)= \frac {C_{2n}^n} {n+1} \]

  • 组合公式2(同上)

\[f(n) = C_{2n}^n - C_{2n}^{n-1} \]

应用背景

通常用来解决一些现实问题,很复杂的题目被看破以后直接用公式算出来便可
举个例子,合法的入栈出栈序列有多少种就是卡特兰数。咱们能够把0当作入栈操做,1当作出栈操做,即0的累计个数不小于1的排列有多少种。
还有不少其余的问题都是卡特兰数,如括号匹配,二叉树的个数,有序树的个数,多边形分红三角形的个数等。学习

  • n+1 个叶子节点的二叉树的数量
    ui

  • n*n的方格地图中,从一个角到另一个角,不跨越对角线的路径数
    spa

  • n+2条边的多边形,能被分割成三角形的方案数
    code

实现过程

来看一道例题作入门吧blog

题目描述

众所周知,在中国古代算筹中,红为正,黑为负……
给定一个\(1*(2n)\)的矩阵,现让你自由地放入红色算筹和黑色算筹,使矩阵平衡[即对于全部的\(i(1<=i<=2n)\),使第\(1-i\)格中红色算筹个数大于等于黑色算筹]
问有多少种方案知足矩阵平衡。递归

输入格式

正整数 nget

输出格式

方案数t对100取模

输入输出样例

Input

2

Output

2

样例解释

红 黑 红 黑
红 红 黑 黑

solution

  • 将红看作入栈操做,黑看为出栈操做,问题即为求不一样的合法的入栈出栈序个数
  • 咱们能够考虑用递推式(1)求卡特兰数,取模更容易

code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main(){
    long long ctl[110],i,j,k,n;
    memset(ctl,0,sizeof(ctl));
    ctl[0]=ctl[1]=1;
    ctl[2]=2;
    scanf("%lld",&n);
    for(i=3;i<=n;++i)
        for(j=0;j<i;++j){
            ctl[i]+=ctl[j]*ctl[i-j-1];
            ctl[i]%=100;
        }
    printf("%lld\n",ctl[n]);
    return 0;
}

这里只给出了第一个式子的应用,更多的请自行查找题进行思惟练习。

Lucas

定义&性质

\(Lucas\)定理是用来求 $ C_n^m mod p $ 的值。
其中\(n\)\(m\)是非负整数,\(p\)是素数。
通常用于\(m,n\)很大而\(p\)很小,抑或是\(n,m\)不大可是大于\(p\)的状况下来求结果。

用处&背景

目前咱们学过几个用来求组合数的方法

  • 最暴力的杨辉三角,用二维数组解决,复杂度 \(O(n^2)\) 效率低下并且因为数组的缘由只能处理很小的数据
  • 将组合式\(C_{n+m}^n \% mod\)转换为\(\frac {(m+n)!} {n! m!} \%mod\),因为除法不能够边除边取\(mod\)因此在处理分母的时候选用了费马小定理,运用逆元的乘法解决。
    看分子的话,\((m+n)!\)在遍历的时候如有元素\(i>mod\),那么取\(mod\)以后整个式子的值就会变成0,而\(Lucas\)的出现,就是为了解决这一问题

引入

针对上述第二种状况,咱们先来看一道例题

旗木双翼

题目描述

菲菲和牛牛在一块n行m列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手。
棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落子,直到填满棋盘时结束。落子的规则是:一个格子能够落子当且仅当这个格子内没有棋子且这个格子的左侧及上方的全部格子内都有棋子。
_Itachi据说有很多学弟在省选现场AC了D1T1,解决了菲菲和牛牛的问题,可是_Itachi据说有的人认为复杂度玄学,_Itachi并不想难为学弟学妹,他想为你们节约时间作剩下的题,因此将简化版的D1T1带给你们。
_Itachi也在一块n行m列的棋盘上下棋,不幸的是_Itachi只有黑棋,不过幸亏只有他一我的玩。如今,_Itachi想知道,一共有多少种可能的棋局(不考虑落子顺序,只考虑棋子位置)。
_Itachi也不会为难学弟学妹们去写高精度,因此只须要告诉_Itachi答案mod 998244353(一个质数)的结果。

输入格式

第一行包括两个整数n,m表示棋盘为n行m列。

输出格式

一个整数表示可能的棋局种数。

样例输入

10 10

样例输出

184756

数据范围与提示

对于 \(20\%\)的数据$n,m \leq10 $
对于 \(30\%\)的数据$n,m\leq20 $
另有 \(20\%\)的数据$n\leq5 $
另有 \(20\%\)的数据$m\leq5 $
对于\(100\%\)的数据$n,m\leq100000 $

solution

能够看到题目当中直接给出了\(mod\)为一个大质数
并且\(m+n\)最大也才二十万,远远小于\(mod\)的数值
因此不会出现取完\(mod\)\(0\)的状况,因此运用逆元求解便可

\[C_{n+m}^m =\frac {(m+n)!} {n!*m!} \% mod \]

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

const int mod = 998244353;

inline int power(int a, int b){
	int ans = 1;
	while(b){
		if(b & 1) ans = (ans % mod) * (a % mod) % mod;
		a = (a % mod) * (a % mod) % mod;
		b >>= 1;
	}
	ans %= mod;
	return ans;
}

signed main(){
	int n = read(), m = read();
	int tmp = 1;
	for(int i = m + n; i >= 1; i--){
		tmp *= i;
		tmp %= mod;
	}
	int n1 = 1;
	int m1 = 1;
	for(int i = 1; i <= n; i++){
		n1 *= i;
		n1 %= mod;
	}
	for(int i = 1; i <= m; i++){
		m1 *= i;
		m1 %= mod;
	}
	int ans = (tmp % mod) * power(n1 ,mod - 2) % mod * power(m1, mod - 2) % mod;
	ans %= mod;
	cout << ans << endl;
	return 0;
}

若是\(mod\)很小
上述算法显然在求阶乘的时候会变成\(0\)
这就须要\(Lucas\)定理了

性质

性质1

\[Lucas(n,m,p)=cm(n\%p,m\%p)*Lucas(\frac n p,\frac m p,p) \]

\[Lucas(x,0,p) =1 \]

其中

\[cm(a,b)=a!*(b!*(a-b)!)^{p-2} (mod \:\ p) \]

$ :\ :\ :\ :\ :\ :\ :\ :\ :\ $ \((a-b)!^{p-2}\)\(a-b\)的逆元,因此能够除一下把式子变成

\[(a!/(a-b)!)*(b!)^{p-2}(mod \:\ p) \]

性质2

\[C_n^mmod p\equiv C_{n\mod p}^{m\mod p}*C_{\lfloor n/p\rfloor}^{\lfloor m/p\rfloor}mod p \]

就是一个\(a,b\)能够拆成\(P\)进制下的乘积

证实

目标方程:

\[\dbinom n m\equiv\dbinom {n\%p} {m\%p} \dbinom {n/p}{m/p}(mod p) \]

显然右侧能够递归处理,咱们只须要推左边就好
证实过程:

\[n=sp+q,m=tp+r,(q,r<p) \]

则目标方程变成

\[\dbinom {sp+q} {tp+r} \equiv \dbinom {s} {t} \dbinom {q} {r} (mod p) \]

下面利用二项式定理&费马小证实一个引理

\[(1+x)^n \equiv (1+x)^{ps} * (1+x)^q (mod \:\ p) \]

\[(1+x)^n\equiv[\sum_{t=0}^p \dbinom p i x^i]^s(mod \:\ p) \]

根据上面的那个性质

\[\equiv (1+x)^q*(1+x^p)^s(mod p) \]

\[\equiv \sum_{i=0}^s \binom s i x^{pt} * \sum_{j=0}^q \binom q j x^j(mod p) \]

则有

\[(1+x)^{sp+q} \equiv\sum_{i=0}^s \binom s i x^{pt} * \sum_{j=0}^q\binom q j x^j(mod p) \]

\[\sum_{k=0}^{sp+q}\binom {sp+q} {k} x^k\equiv\sum_{i=0}^s \binom s i x^{pt} * \sum_{j=0}^q\binom q j x^j(mod p) \]

左边\(x^{tp+r}\)的系数为\(\binom {sp+q}{tp+r}\)

右边\(x^{tp+r}\)的系数为\(\binom s t\binom q r\)

故系数相等,原命题得证

\[\dbinom {sp+q} {tp+r} \equiv \dbinom {s} {t} \dbinom {q} {r} (mod p) \]

\[\dbinom n m\equiv\dbinom {n\%p} {m\%p} \dbinom {n/p}{m/p}(mod p) \]

\[Lucas(n,m,p)=cm(n\%p,m\%p)*Lucas(\frac n p,\frac m p,p) \]

\[Lucas(x,0,p) =1 \]

(以上证实结束)

实现过程

  • 目标运算结果\(C_n^m \:\ \%p\)
  • 注意输入的\(n,m\)中,可能会出现\(n\%p<m%p\),那么直接输出0便可。由于若\(n<m\),则\(C_n^m=0\)
  • 在求逆元时,运用快速幂求其\(p-2\)次方
  • 若是要大量运用\(Lucas\)定理,建议使用杨辉三角打组合数表或将阶乘以及阶乘逆元打表

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

int n, m, p;

inline int power(int a, int b){
	int ans = 1;
	while(b){
		if(b & 1) ans = (ans % p) * (a % p) % p;
		a = (a % p) * (a % p) % p;
		b >>= 1;
	}
	ans %= p;
	return ans;
}

inline int getc(int n, int m){
	if(n < m) return 0;
	if(m > n - m) m = n - m;
	int s1 = 1, s2 = 1;
	for(int i = 0; i < m; i++){
		s1 = s1 * (n - i) % p;//(n-m)!/n!
		s2 = s2 * (i + 1) % p;//m!
	}
	return s1 * power(s2, p - 2) % p;
}

inline int lucas(int n, int m){
	if(m == 0) return 1;
	return getc(n % p, m % p) * lucas(n / p, m / p) % p;
}

signed main(){
	int t = read();
	while(t--){
		n = read(), m = read(), p = read();
		cout << lucas(n + m, m) << endl;
	}
	return 0;
}
相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息