组合数的求法
1.打表法
//组合数打表模板,适用于N<=3000 //c[i][j]表示从i个中选j个的选法。 long long C[N][N]; void get_C(int maxn) { C[0][0] = 1; for(int i=1;i<=maxn;i++) { C[i][0] = 1; for(int j=1;j<=i;j++) C[i][j] = C[i-1][j]+C[i-1][j-1]; //C[i][j] = (C[i-1][j]+C[i-1][j-1])%MOD; } }
2.逆元法
C(n,k)==A(n,n) / ( A(k,k)*A(n-k,n-k))
void inint(){ j[1]=1; for(ll i=2;i<maxn;i++){ j[i]=i*j[i-1]%M;//阶乘打表 } } ll C(ll n,ll m){ ll ans1=kuai(j[m],M-2)%M*kuai(j[n-m],M-2)%M; return j[n]*ans1%M; }
3卢卡斯定理
1≤n,m,p≤10^5,1≤T≤10,p为素数
ll qpow(ll a,ll b){ ll ans=1; while(b){ if(b&1){ ans=(ans*a)%p; } a=(a*a)%p; b>>=1; } return ans; } ll C(ll n,ll m){ if(n<m) return 0; if(m>n-m){ m=n-m; } ll a=1,b=1; for(int i=0;i<m;i++){ a=(a*(n-i))%p; b=(b*(i+1))%p; } return a*qpow(b,p-2)%p; } ll Lucas(ll n,ll m){ if(m==0){ return 1; } else{ return Lucas(n/p,m/p)*C(n%p,m%p)%p; } }
组合数求和(N一定)
例:
据说一间6人宿舍有7个聊天群^_^。但是这个数字可以更大:因为每三个或更多的人可以组成一个聊天组,所以可以有42个不同的聊天组。
假设一个宿舍里有N个人,每K个或更多的人可以组成一个聊天组,有多少个不同的聊天组?
解释:C(6,3)+C(6,4)+C(6,5)+C(6,6)
输入从一行开始,其中包含一个整数T,这是测试用例的数量。
每个测试用例包含一行,其中有两个整数N和K,表示一个宿舍的人数和组成一个聊天组的最小人数。
1≤T≤100。
1≤N≤1e9。
3≤K≤1e5。
输出
对于每个测试用例,输出一行包含“案例#x: y”的代码,其中x是测试用例号(从1开始),y是不同聊天组的数量,模块为1000000007。
解析:因为N的范围是1e9以内,所以不能用逆元的方法
#pragma GCC optimize(2) #include <iostream> #include <string> #include <cstdio> #include <stack> #include <queue> #include <vector> #include <cstring> #include <algorithm> typedef long long ll; ll read(){ ll x=0; ll f=1; char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();} while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } using namespace std; const int maxn=1e6+100; const ll mod=1e9+7; const int N=5e5+10; //C(n,k)+C(n,k+1)+C(n,k+2)+-----C(n,n) //C(n,1)+C(n,2)+C(n,3)-----C(n,n)-C(n,1)-C(n,2)-C(n,3)----C(n,k-1) //c(n,k)==A(n,n)/(A(k,k)*A(n-k,n-k) ll n,k; ll qpow(ll a,ll b){ ll ans=1; while(b){ if(b&1){ ans=(ans*a)%mod; } a=(a*a)%mod; b>>=1; } return ans%mod; } ll inv(ll a){ return qpow(a,mod-2)%mod; } int main(){ int t; cin>>t; int Case=1; while(t--){ scanf("%lld%lld",&n,&k); if(k>n){ printf("Case #%d: 0 ",Case++); continue; } ll ans=qpow(2,n); ll sum=1+n;//c(n,0)+c(n,1) ll temp=n;//C(n,1) //sum=1+n //c(n,i+1)=c(n,i)*(n-i)-/(i+1) for(ll i=1;i<=k-2;i++){ temp=((temp*(n-i)%mod)*inv(i+1))%mod; sum=(sum+temp)%mod; } printf("Case #%d: %lld ",Case++,(ans-sum+mod)%mod); } }
Cmn=C(m/p)(n/p)∗C(m%p)(n%p)(mod p)
观察上述表达式,可知 n%p和 m%p 一定是小于 p 的数,可以直接求解。
C(m/p)(n/p) 可以继续用 Lucas 定理求解,这也就要求 p 的范围不能够太大,一般在 1e5 左右。
边界条件:当 m=0 的时候,返回 1 。
复杂度:O(logn+p)