数论组合数

组合数的求法

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\n",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\n",Case++,(ans-sum+mod)%mod);
    } 
} 

 

 


Cmn=C(m/p)(n/p)C(m%p)(n%p)(mod p)Cnm=C(n/p)(m/p)∗C(n%p)(m%p)(mod p)

观察上述表达式,可知 n%pn%p和 m%pm%p 一定是小于 pp 的数,可以直接求解。
C(m/p)(n/p)C(n/p)(m/p) 可以继续用 LucasLucas 定理求解,这也就要求 pp 的范围不能够太大,一般在 1e51e5 左右。
边界条件:当 m=0m=0 的时候,返回 11 。
复杂度:O(logn+p)

posted @ 2020-10-19 20:16  哎呦哎(iui)  阅读(156)  评论(0编辑  收藏  举报