卢卡斯定理
公式
若n,m为整数,p为质数
这个式子有什么作用呢,最简单的一种就是求组合数。
有时候n,m过大,可能是p的倍数,这时候n,m对于p没有逆元,自然没办法用费马小定理求逆元。这个时候我们就需要卢卡斯定理了
求组合数步骤
- 1.求\(C_{n\bmod p}^{m\bmod p}\)
n,m显然比p小,直接费马小定理求 - 2.递归求\(C_{n/p}^{m/p}\)
就是这么简单,很好理解。
P3807 【模板】卢卡斯定理/Lucas 定理
模板题:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,p;
int t;
int pre[100010];
void prev(){//lucas()前面都是常规的费马小定理求组合数
pre[0]=1;
for(int i=1;i<=100000;i++){
pre[i]=(pre[i-1]*i);
pre[i]%=p;
}
}
int qpow(int aa,int bb){
int vl=1,hl=aa;
hl%=p;
while(bb){
if(bb%2){
vl*=hl;
vl%=p;
}
hl*=hl;
hl%=p;
bb/=2;
}
return vl;
}
int C(int aa,int bb){
if(aa<bb) return 0;
int ans=pre[aa];
ans*=qpow(pre[aa-bb],p-2);
ans%=p;
ans*=qpow(pre[bb],p-2);
ans%=p;
return ans;
}
int lucas(int aa,int bb){
if(bb==0) return 1;
int val=C(aa%p,bb%p);
val*=lucas(aa/p,bb/p);
val%=p;
return val;
}
signed main(){
cin>>t;
while(t--){
cin>>n>>m>>p;
prev();
cout<<lucas(n+m,m)<<endl;
}
return 0;
}
POJ - 3219 Binomial Coefficients
这道题当然可以把模数设成2,直接求,看结果是0还是1,但是这样显得太没水平,也很无聊。
考虑有没有单次O(1)的做法。
我们回到卢卡斯定理的式子:
观察前面一项,不难发现\(n\bmod p\)即为n在p进制下的最后一位,后一项递归下去的结果就是把n,m在p进制下的每一位拆出来,分别计算组合数并相乘。
有了这一点以后我们发现p=2,也就是n,m在p进制下的每一位都是0或1,其每一位的组合数只可能是下面几种:
1.\(C_{0}^{0}=1\)
2.\(C_{0}^{1}=0\)
3.\(C_{1}^{0}=1\)
4.\(C_{1}^{1}=1\)
于是我们发现只要出现存在\(C_{0}^{1}\),整体结果为0,否则为1。而\(C_{0}^{1}\)意味着2进制下有一位上n[i]=0,m[i]=1。
如果对于所有i使得m[i]=1,有n[i]=1,则一定有(n-m)&m==0(正好n-m把那些位置减掉了),此时结果为偶数,否则为奇数。
#include<iostream>
using namespace std;
int main(){
int n,m;
while(cin>>n>>m){
if((n-m)&m) cout<<0<<endl;
else cout<<1<<endl;
}
}
HDU - 3304 Interesting Yang Yui Triangle
分析思路和上一题差不多。反方向计算不被p整除的数量。此时p进制下n的每一位,必定大于等于m这一位,否则其值为0(这是显然的,比如不可能从两个里选出三个),那么对于n的p进制下的每一位合法的m[i]满足0<=m[i]<=n[i],再乘法原理把每一为的方案数相乘即可
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int main(){
int p,n;
int cas=1;
while(scanf("%d %d", &p, &n)!=EOF){
if(p==0 && n==0) break;
int sum=1;
while(n){
sum*=n%p+1;
n/=p;
}
printf("Case %d: %04d\n", cas++, sum%10000);
}
return 0;
}
HDU - 3037 Saving Beans
题目里说的是不超过m颗松子,发现固定松子个数可以用插板法计算,列出计算公式:
也就是这样一个式子:
(c(i,j)表示\(C_{i}^{j}\))
我们人为凑一个\(c(n-1,n-2)\)上去(反正是0),就会发现可以一直杨辉三角合并下去(\(c(i,j)+c(i,j+1)=c(i+1,j+1)\)),最后得到\(c(n+m,m)\)
#include<iostream>
#define int long long
using namespace std;
int n,m,p;
int t;
int pre[100010];
inline int read(){
char ch;int x=0;bool f=false;
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
if(f) x=-x;
return x;
}
void pref(int p){
for(int i=1;i<=p;i++){
pre[i]=pre[i-1]*i;
pre[i]%=p;
}
}
int qpow(int a, int b)//快速幂
{
int res = 1;
for(; b; b>>=1, a=a*a%p)
res = res * (b&1?a:1) % p;
return res;
}
int C(int aa,int bb){
if(aa<bb) return 0;
int val=pre[aa];
val*=qpow(pre[aa-bb],p-2);
val%=p;
val*=qpow(pre[bb],p-2);
val%=p;
return val;
}
int lucas(int aa,int bb){
if(bb==0) return 1;
int val=C(aa%p,bb%p);
val*=lucas(aa/p,bb/p);
val%=p;
return val;
}
int solve(int aa,int bb){
if(aa<0 || bb<0) return 0;
int sum=0;
for(int i=0;i<=bb;i++){
sum+=lucas(aa,i);
sum%=p;
sum+=lucas(aa,i+1);
sum%=p;
//sum-=lucas(aa,i+1);
//sum+=p;
//sum%=p;
}
return sum;
}
signed main(){
//p=1000000000;
//cout<<qpow(2,3)<<endl;
pre[0]=1;
t=read();
while(t--){
cin>>n>>m>>p;
pref(p);
cout<<lucas(n+m,m)<<endl;
}
return 0;
}
HDU - 5226 Tom and matrix
和上一题差不多的处理思路,每一列凑一个值,最后算出来只剩两项,再减去凑的值(作者一直T,好心人帮忙调调代码T^T)
#include<iostream>
#define int long long
using namespace std;
int a,b,c,d,p;
int pre[100010];
inline int read(){
char ch;int x=0;bool f=false;
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
if(f) x=-x;
return x;
}
void pref(int p){
for(int i=1;i<=p;i++){
pre[i]=pre[i-1]*i;
pre[i]%=p;
}
}
int qpow(int aa,int bb){
int vl=1,hl=aa;
hl%=p;
while(bb){
if(bb%2){
vl*=hl;
vl%=p;
}
hl*=hl;
hl%=p;
bb/=2;
}
return vl;
}
int C(int aa,int bb){
if(aa<bb) return 0;
if(bb==0) return 1;
int val=pre[aa];
val*=qpow(pre[aa-bb],p-2);
val%=p;
val*=qpow(pre[bb],p-2);
val%=p;
return val;
}
int lucas(int aa,int bb){
if(aa<p &&bb<p) return C(aa,bb);
int val=C(aa%p,bb%p)*lucas(aa/p,bb/p);
val%=p;
return val;
}
signed main(){
pre[0]=1;
while(cin>>a>>b>>c>>d>>p){
pref(p);
if(p==1){
cout<<0<<"\n";
continue;
}
int ans=0;
for(int i=b;i<=d;i++){
ans+=lucas(c,i);
ans+=lucas(c,i+1);
ans-=lucas(a,i+1);
ans+=p;
ans%=p;
}
cout<<ans<<"\n";
}
return 0;
}