初学Lucas定理(2019.8.10)

本文深入解析了Lucas定理在解决大数组合数计算问题中的高效运用,通过递归调用与快速幂算法,将原本复杂的组合数计算转化为简单递归过程,显著减少了预处理步骤与时间复杂度。

昨天怀着忐忑不安的心情来了学校
今天晚上看了一下Lucas定理
好像是这么一回事

Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p)
也就是说从n中取m个数模p
A=a[n]a[n-1]…a[0],B=b[n]b[n-1]…b[0]。则组合数C(A,B)与C(a[n],b[n])C(a[n-1],b[n-1])…*C(a[0],b[0]) modp同余,也就是说:
Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p)
(证明还不太会的感觉,得学学嘿,先写完这篇QAQ)
这样的话把原本难以实现的变成了递归的调用
不断地往下一级搜索的同时乘以C(a[n],b[n])
这样就减少了很多预处理什么的麻烦(比如我一开始还想着要不要先把这个a,b数组求出来存到数组里面,其实根本没必要的)
实现起来很简便

然后的话,modp意义下的话,除以一个数字等于乘以这个数字的乘法逆元
求乘法逆元的话会有两种方法
1.当p是质数,可以直接用费马小定理进行求解,配上快速幂,这里就不说了
2.可以用线性递推的方式求
3.拓展欧几里得算法,就是等价于ax+py=1,求出x就Ok(假设的是a,p已经确定)
当然是第一种快一些,而且题目中有提到这个数字是个质数,所以是不必要一定要用第二个的
然后预处理阶乘的话就是递推
所以递推+递归+快速幂
这题好像就写出来了

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[100010];
ll pow(ll y,ll z,ll p){//y的z次方取摸p
	ll ans=1;
	while(z){
		if(z&1){
			ans=ans*y%p;
		}
		y=y*y%p;
		z=z/2;
	}
	return ans%p; 
}
ll C(ll n,ll m,ll p){
	if(m>n) return 0;
	return (a[n]*pow(a[m],p-2,p))%p*pow(a[n-m],p-2,p)%p;
}
ll Lucas(ll n,ll m,ll p){
	if(!m) return 1;
	return C(n%p,m%p,p)*Lucas(n/p,m/p,p)%p; 
}
int main(){
	int t;
	cin>>t;
	while(t--){
		ll n,m,p;
		cin>>n>>m>>p;
		a[0]=1;
		for(ll i=1;i<=p;i++) a[i]=(a[i-1]*i)%p;
		cout<<Lucas(n+m,n,p)<<endl;
	}	
}

这里就是洛谷上面的模板提的实现代码(借鉴zcy大佬)
感觉Lucas定理的实现真是有用,如果没有这个的话,对于这种题肯定想到的是组合数的递推公式,首先的话,如果按照常理,这个二维数组是不够大的,空间复杂度不够,其次,就是说时间复杂度很大,尤其是在推组合数的时候,就相当于一个面积为n*n/2的一个三角形,一般的题目是承受不了这么大的时间复杂度。所以Lucas定理很好的解决了问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值