盧卡斯定理

星河倒注發表於2024-11-03

公式

若n,m為整數,p為質數

\[C_{n}^{m}\bmod p= C_{n\bmod p}^{m\bmod p}\times C_{n/p}^{m/p}\bmod 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)的做法。
我們回到盧卡斯定理的式子:

\[C_{n}^{m}\bmod p= C_{n\bmod p}^{m\bmod p}\times C_{n/p}^{m/p}\bmod p \]

觀察前面一項,不難發現\(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顆松子,發現固定松子個數可以用插板法計算,列出計算公式:

\[\sum_{i=0}^{m} C_{i+n-1}^{n-1} \]

也就是這樣一個式子:

\[c(n-1,n-1)+c(n,n-1)+...+c(n+m-1,n-1) \]

(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;
}