昨天怀着忐忑不安的心情来了学校
今天晚上看了一下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定理很好的解决了问题。