FZU - 2020 组合(乘法逆元)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a54665sdgf/article/details/81351708

给出组合数C(n,m), 表示从n个元素中选出m个元素的方案数。例如C(5,2) = 10, C(4,2) = 6.可是当n,m比较大的时候,C(n,m)很大!于是xiaobo希望你输出 C(n,m) mod p的值!

思路:利用递推式C(n,m)=C(n,m-1)*(n-m+1)/m可以很方便地求出C(n,m)的值,由于有取模运算,所以不能直接除以m,取而代之的是乘以m在mod p下的逆元。

注意在乘逆元之前一定要先取一次模!

主程序部分:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<algorithm>
#include<map>
#include<stack>
#include<bitset>
#include<list>
#define FRER() freopen("i.txt","r",stdin)

using namespace std;
typedef long long ll;

ll C(ll n,ll m,ll mod)
{
    m=min(m,n-m);
    ll s=1;
    for(ll i=1; i<=m; ++i)
        s=s*(n-i+1)%mod*Inv(i,mod)%mod;
    return s;
}

int main()
{
    //FRER();
    ll n,m,mod;
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d%I64d",&n,&m,&mod);
        printf("%I64d\n",C(n,m,mod));
    }

    return 0;
}

求除法逆元主要有两种方法,一种是用扩展欧几里得,另一种是用费马小定理,代码分别如下:

扩展欧几里得:

ll exgcd(ll a,ll b,ll& x,ll& y)
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    ll r=exgcd(b,a%b,x,y);
    ll t=y;
    y=x-(a/b)*y;
    x=t;
    return r;
}

ll Inv(ll n,ll mod)
{
    ll x,y;
    exgcd(n,mod,x,y);
    return ((x%mod)+mod)%mod;
}

费马小定理:

ll Pow(ll x,ll p,ll mod)
{
    ll ret=1;
    while(p)
    {
        if(p&1)
            ret=ret*x%mod;
        x=x*x%mod;
        p>>=1;
    }
    return ret;
}

ll Inv(ll n,ll mod)
{
    return Pow(n,mod-2,mod);
}

用扩展欧几里得的方法似乎要比费马小定理快一些,跑了203ms,而用费马小定理跑了437ms...

ps:此题还有一种解法是用Lucas定理,复杂度要低许多。不过这道题本身的数据量不是很大,用以上方法足矣。

猜你喜欢

转载自blog.csdn.net/a54665sdgf/article/details/81351708