组合数总结

☆组合数的若干求法☆

一、杨辉三角法

公式:(C_m^n=C_{m-1}^n+C_{m-1}^{n-1})

要求:无

复杂度:(O(N^2))~(O(1))

适用性:较广

二、逆元法

公式:(C_m^n=frac{m!}{n! imes (m-n)!})

要求:模数(p)为质数

复杂度:(O(logp+m))(计算逆元+预处理阶乘)

预处理后可做到:(O(m))~(O(1))

适用性:(m)较小时

三、lucas定理+逆元法

公式:

(C_m^n=frac{m!}{n! imes (m-n)!})

(C_m^n equiv C_{m/p}^{n/p} imes C_{m \% p}^{n \% p} (mod p))

要求:模数(p)为质数

复杂度:(O(logmlogp+p))

预处理后可做到:(O(p))~(O(logm))

适用性:(p)较小时,(m)较大时

四、lucas定理+逆元+CRT合并法

公式同三

要求:模数分解质因数后,所有的质因子指数都为(1)

具体的:我们在每一个质因子下的答案,然后适用CRT进行合并

复杂度:(O(tlogmaxplogm+tlogp))(maxp为p的最大质因子,t是质因子个数,后面一项是玄学,假装是CRT合并的复杂度)

预处理后:(O(tlogmaxp))~(O(tlogm))

适用性:小,但(p,m,n)可以较大且(p)可以不是质数

五、扩展lucas

要求:无

到这里,我们把上面所有的求法都抛开吧,不一样不一样的,别被误导了

看看最开始的东西,求

(frac{m!}{n! imes (m-n)!} mod p)

先把(p)唯一分解掉

(p=prod_{i=1}^t p_i^{c_i})

如果我们可以求出每一个(frac{m!}{n! imes (m-n)!} mod p_i^{c_i}),那么很显然是可以CRT合并的。

问题转换成了如何求

(frac{m!}{n! imes (m-n)!} mod p_i^{c_i})

我们考虑把计算式中所有含(p_i)的项给搞出来,然后拿到一边去

拿走的一部分直接快速幂计算,没拿走的一部分也可以直接求逆元了

我们考虑 没拿走的一部分的计算 和 含(p_i)的项的个数的计算公式

首先考虑(n!)中含有多少个(p_i)

(f(n))代表(n!)中含(p_i)的个数

(n!=1 imes 2 imes 3 imes ... imes n)

我们把乘积项中所有含(p_i)的项取出来,这些项一定是(1 imes p_i),(2 imes p_i)...(lfloor n/p_i floor imes p_i),对每一项取出一个(p_i),则剩下的就是(lfloor n/p_i floor !),很好子问题

我们得到了递归计算的公式:

(f(x)=f(lfloor x/p_i floor)+lfloor x/p_i floor)

(f(0)=0)

然后我们考虑如何计算

(frac{n!}{f(n)*p_i})

我们把乘积项按(p_i^{c_i})分成一节一节的,然后把所有的(p_i)的倍数项拿走

因为(x equiv x+p_i^{c_i} (mod p_i^{c_i})),所以每一节的乘积是一样的,快速幂直接算,剩下的不足一个循环节的暴力算

然后我们算(p_i)的倍数项,发现同样的,我们把每一项拿走一个,就剩下了子问题,真是皆大欢喜,问题就解决啦

分析一下复杂度

求除过以后的阶乘(O(log_{p_i}m+p_i^{c_i}))

求质因子个数(O(log_{p_i}m))

求逆元和CRT合并的复杂度应该比较低。。

大概就是这样 实际上是算不下去啦

适用性:非常广,但是不太好打

Code:

#include <cstdio>
#define ll long long
ll mod;
ll quick_pow(ll d,ll k,ll p)
{
    ll f=1;
    while(k)
    {
        if(k&1) f=f*d%p;
        d=d*d%p;
        k>>=1;
    }
    return f;
}
ll fac(ll n,ll p,ll d)
{
    if(!n) return 1;
    ll f=1,res=1;
    for(ll i=2;i<=p;i++)
        if(i%d) (f*=i)%=p;
    for(ll i=2;i<=n%p;i++)
        if(i%d) (res*=i)%=p;
    return fac(n/d,p,d)*quick_pow(f,n/p,p)%p*res%p;
}
void exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b)
    {
        x=1,y=0;
        return;
    }
    exgcd(b,a%b,x,y);
    ll tmp=x;
    x=y;
    y=tmp-a/b*y;
}
ll inv(ll n,ll p)
{
    ll x,y;
    exgcd(n,p,x,y);
    return (x%p+p)%p;
}
ll C(ll n,ll m,ll p,ll d)
{
    ll ct=0;
    for(ll i=m;i;i/=d) ct+=i/d;
    for(ll i=n;i;i/=d) ct-=i/d;
    for(ll i=m-n;i;i/=d) ct-=i/d;
    return fac(m,p,d)*inv(fac(n,p,d),p)%p*inv(fac(m-n,p,d),p)%p*quick_pow(d,ct,p)%p;
}
ll CRT(ll a,ll b)
{
    return mod/b*a%mod*inv(mod/b,b)%mod;
}
ll exlucas(ll n,ll m,ll p)
{
    ll ans=0;
    for(ll d=1,i=2;i*i<=p;d=1,i++)
    {
        if(p%i==0)
        {
            while(p%i==0)
            {
                p/=i;
                d*=i;
            }
            (ans+=CRT(C(n,m,d,i),d))%=mod;
        }
    }
    if(p!=1)
        (ans+=CRT(C(n,m,p,p),p))%=mod;
    return ans;
}
int main()
{
    ll m,n;
    scanf("%lld%lld%lld",&m,&n,&mod);
    printf("%lld
",exlucas(n,m,mod));
    return 0;
}

总结:灵活使用吧,最好都掌握一下


2018.8.27

原文地址:https://www.cnblogs.com/butterflydew/p/9540250.html