版权声明:本文为博主原创文章,未经博主允许不得转载。 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定理,复杂度要低许多。不过这道题本身的数据量不是很大,用以上方法足矣。