组合数总结

☆组合数的若干求法☆

一、杨辉三角法

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

要求:无

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

适用性:较广

二、逆元法

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

要求:模数\(p\)为质数

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

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

适用性:\(m\)较小时

三、lucas定理+逆元法

公式:

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

\(C_m^n \equiv C_{m/p}^{n/p} \times 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! \times (m-n)!} \ mod \ p\)

先把\(p\)唯一分解掉

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

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

问题转换成了如何求

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

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

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

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

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

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

\(n!=1 \times 2 \times 3 \times ... \times n\)

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

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

\(f(x)=f(\lfloor x/p_i \rfloor)+\lfloor x/p_i \rfloor\)

\(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\n",exlucas(n,m,mod));
    return 0;
}

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


2018.8.27

posted @ 2018-08-27 09:02  露迭月  阅读(781)  评论(0编辑  收藏  举报