组合数实现及卢卡斯定理证明

本文介绍了三种计算组合数的方法:暴力实现、预处理阶乘及逆元、以及利用卢卡斯定理优化。重点讲解了卢卡斯定理在大数情况下的高效计算,并提供了相应的代码实现和证明过程。

一、通过定义实现

Cnm=n!(n−m)!C_n^m = \frac{n!}{(n-m)!}Cnm=(nm)!n!

1、暴力实现
适用于 nnnmmm 非常小的时候,不然会爆longlong
优势:代码量小
可以加取模

时间复杂度O(m)O(m)O(m)

#define ll long long
ll C(int a, int b)
{
	if (a < 0 || b < 0 || a < b) return 0;
    ll res = 1;
    for (int i = 1 , j = a; i <= b; i ++ , j--) 
    	res = res * j / i ;
    return res;
}

2、预处理阶乘及逆元实现

O(n)预处理,O(1)查询O(n)预处理 , O(1)查询O(n)O(1)

const int mod = 1e9 + 7;
const int N = 1e6 + 7;
ll jc[N],fjc[N];
ll ksm(ll a, ll b)
{
    ll res = 1;
    while (b)
    {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
ll C(int a, int b)
{
	if (a < 0 || b < 0 || b > a) return 0;
    if (b == 0 || a == b) return 1;
    return jc[a] * fjc[a - b] % mod * fjc[b] % mod;
}
void init()
{
    jc[0] = 1;
    for (int i = 1; i < N; i++) 
    	jc[i] = jc[i - 1] * i % mod;
    fjc[N - 1] = ksm(jc[N - 1], mod - 2);
    for (int i = N - 2; i; i--)  
    	fjc[i] = fjc[i + 1] * (i + 1) % mod;
}

二、通过递推式实现

Cnm=Cn−1m+Cn−1m−1C_n^m=C_{n-1}^m+C_{n-1}^{m-1}Cnm=Cn1m+Cn1m1
Ci0=Cii=1C_i^0=C_i^i=1Ci0=Cii=1

O(nm)预处理,O(1)查询O(nm)预处理,O(1)查询O(nm)O(1)

const int N = 3000
int C[N][N];
void init()
{
	C[0][0] = 1;
	for(int i = 1 ; i < N ; i ++)
	{
		C[i][0] = C[0][i] = 1;
		for(int j = 1 ; j < i  ; j ++)
		{
			C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod;
		}
	}
}

三、卢卡斯定理

定理内容:Cnm≡Cn%pm%p∗Cn/pm/p(mod p)C_n^m \equiv C_{n\%p}^{m\%p} * C_{n/p}^{m/p}(mod\ p)CnmCn%pm%pCn/pm/p(mod p)

适用范围:nnnmmm 很大,但模数不大且是素数时

时间复杂度O(logpm+p)O(log_p^m+p)O(logpm+p)
注意更换模数之后要重新初始化

const int N = 1e6 + 10;
int mod;
ll jc[N],fjc[N];
ll ksm(ll a, ll b)
{
    ll res = 1;
    while (b)
    {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
ll C(int a, int b)
{
	if (a < 0 || b < 0 || a < b) return 0;
    if (b == 0 || a == b) return 1;
    return jc[a] * fjc[a - b] % mod * fjc[b] % mod;
}
void init()	//使用预处理阶乘方法求组合数,注意上界为mod而不是之前的N
{
    jc[0] = 1;
    for (int i = 1; i < mod; i++)
    	jc[i] = jc[i - 1] * i % mod;
    fjc[mod - 1] = ksm(jc[mod - 1], mod - 2);
    for (int i = mod - 2; i; i--)
    	fjc[i] = fjc[i + 1] * (i + 1) % mod;
}

int LC(ll a , ll b)
{
    if(max(a,b) < mod) return C(a,b);
    return C(a%mod,b%mod) * LC(a/mod , b/mod) % mod;
}

证明:
Cnm≡Cn%pm%p∗Cn/pm/p(mod p)C_n^m \equiv C_{n\%p}^{m\%p} * C_{n/p}^{m/p}(mod\ p)CnmCn%pm%pCn/pm/p(mod p)

下面过程中将省略 (mod p)(mod\ p)(mod p),注意同余号 “≡”“\equiv” 和等号“=”“=”=的区别。

(1+x)p(1+x)^p(1+x)p
=∑i=0pCpixi= \sum _{i=0}^{p}C_p^ix^i=i=0pCpixi
=1+xp+∑i=1p−1Cpixi= 1+x^p+\sum _{i=1}^{p-1}C_p^ix^i=1+xp+i=1p1Cpixi

注意 Cpi=p!i!(p−i)!=(p−1)!∗pi!(p−i)!C_p^i = \frac{p!}{i!(p-i)!} = \frac{(p-1)!*p}{i!(p-i)!}Cpi=i!(pi)!p!=i!(pi)!(p1)!p
由于 ppp 是素数所以分子中的 ppp 是不会被约分掉的,
所以 0<i<p0<i<p0<i<p 时,有Cpi%p=0C_p^i\%p = 0Cpi%p=0

所以得到 (1+x)p≡1+xp(1+x)^p \equiv 1+x^p(1+x)p1+xp

nnnmmm 转化为 ppp 进制数
即设:n=∑i=0kni∗pin=\sum_{i=0}^kn_i*p^in=i=0knipim=∑i=0kmi∗pim=\sum_{i=0}^km_i*p^im=i=0kmipi

(1+x)n=∏i=0k(1+x)ni∗pi=∏i=0k((1+x)pi)ni(1+x)^n=\prod_{i=0}^{k}(1+x)^{n_i*p_i}=\prod_{i=0}^{k}((1+x)^{p_i})^{n_i}(1+x)n=i=0k(1+x)nipi=i=0k((1+x)pi)ni

(1+x)n≡∏i=0k(1+xpi)ni(1+x)^n\equiv \prod_{i=0}^{k}(1+x^{p_i})^{n_i}(1+x)ni=0k(1+xpi)ni

左式中 xmx^mxm 的系数为 CnmC_n^mCnm

右式中xpimix^{p_im_i}xpimi的系数为 CnimiC_{n_i}^{m_i}Cnimi
∏i=0kxpimi=xm\prod_{i=0}^{k}x^{p_im_i} = x^mi=0kxpimi=xm

所以Cnm≡∏i=0kCnimiC_{n}^{m} \equiv \prod_{i=0}^{k}C_{n_i}^{m_i}Cnmi=0kCnimi,这个式子也就是卢卡斯定理的非递归形式。

继续推导:
∏i=0kCnimi\prod_{i=0}^{k}C_{n_i}^{m_i}i=0kCnimi
=Cn0m0∏i=1kCnimi=C_{n_0}^{m_0}\prod_{i=1}^{k}C_{n_i}^{m_i}=Cn0m0i=1kCnimi
根据进制转换的性质可以得到
上式 =Cn%pm%p∗Cn/pm/p= C_{n\%p}^{m\%p} * C_{n/p}^{m/p}=Cn%pm%pCn/pm/p
证毕。

如有错误,欢迎指正。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值