方案一:纯暴力方案
C(n,m)=n * (n-1) * (n-2) * (n-m+1) / m!
typedef long long ll;
ll Combination(ll n,ll m)
{
ll ans=1;
for(int i=n;i>=n-m+1;i--)
ans*=i;
for(int i=m;i>=1;i--)
ans/=i;
return ans;
}
int main()
{
ll a,b;
cin >> a >> b;
cout << Combination(a,b);
}
方案二:打表方案
C(n,m)=C(n-1,m)+C(n-1,m-1)
//如果不求余,最多能算到60
ll C[Max][Max];
void Combination()
{
for(int i=0;i<Max;i++)
C[i][0]=1;
for(int i=1;i<Max;i++)
for(int j=1;j<Max;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
int main()
{
Combination();
for(int i=1;i<Max;i++)
{
for(int j=1;j<=i;j++)
cout << C[i][j] << " ";
cout << endl;
}
}
方案三:质因数分解
C(n,m)=n! / ( m! * (n-m)! ),设n!分解因式后,质因数p的次数为a;对应地m!分解后p的次数为b;(n-m)!分解后p的次数为c;则C(n,m)分解后,p的次数为a-b-c。计算出所有质因子的次数,它们的积即为答案,即C(n,m)=p1a1-b1-c1 * p2a2-b2-c2…pkak-bk-ck。n!分解后p的次数为:n/p+n/p2+…+n/pk。
算法的时间复杂度比前两种方案都低,基本上跟n以内的素数个数呈线性关系,而素数个数通常比n都小几个数量级,例如100万以内的素数不到8万个。用筛法生成素数的时间接近线性。该方案1秒钟能计算 1kw数量级的组合数。如果要计算更大,内存和时间消耗都比较大。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int MOD = 100007;
const int maxn = 1000001;
bool a[maxn]={false};
vector <int> prim_produce() //生成素数序列
{
vector <int> vc;
vc.push_back(2);
int i,j;
for(i=3;i*i<=maxn;i+=2)
{
if(!a[i])
{
vc.push_back(i);
for(j=i*i;j<=maxn;j+=i)
{
a[j]=true;
}
}
}
while(i<maxn)
{
if(!a[i]) vc.push_back(i);
i+=2;
}
return vc;
}
//计算n!素数p的指数
int cal(int x,int p)
{
int ans=0;
long long re=p;
while(x>=re)
{
ans+=x/re;
re*=p;
}
return ans;
}
int Pow(long long n,int k) //二分求n的k次方
{
long long ans=1;
while(k)
{
if(k&1)
ans=ans*n%MOD;
n=(n*n)%MOD;
k>>=1;
}
return ans;
}
int Combination(int n,int m) //计算公式
{
vector <int> prim=prim_produce();
long long ans=1;
int num;
for(int i=0;i<prim.size()&&prim[i]<=n;i++)
{
num=cal(n,prim[i])-cal(m,prim[i])-cal(n-m,prim[i]);
ans=(ans*Pow(prim[i],num))%MOD;
}
return ans;
}
int main(void)
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
printf("%d\n",Combination(n,m));
}
return 0;
}
方案四:Lucas定理
设p是一个素数(题目中要求取模的数也是素数),将n,m均转化为p进制数,表示如下:
满足下式:
**C(n,m)=C(n0,m0) * C(n1,m1)…(mod p),**即C(n,m)模p等于p进制数上各位的C(ni,mi)模p的乘积。利用该定理,可以将计算较大的C(n,m)转化成计算各个较小的C(ni,mi)。
该方案能支持整型范围内所有数的组合数计算,甚至支持64位整数,注意中途溢出处理。该算法的时间复杂度跟n几乎不相关了,可以认为算法复杂度在常数和对数之间。
#include <stdio.h>
const int M = 2013;
int ff[M+5]; //打表,记录n!,避免重复计算
//求最大公因数
int gcd(int a,int b)
{
if(b==0)
return a;
else
return gcd(b,a%b);
}
//解线性同余方程,扩展欧几里德定理
int x,y;
void Extended_gcd(int a,int b)
{
if(b==0)
{
x=1;
y=0;
}
else
{
Extended_gcd(b,a%b);
long t=x;
x=y;
y=t-(a/b)*y;
}
}
//计算不大的C(n,m)
int C(int a,int b)
{
if(b>a)
return 0;
b=(ff[a-b]*ff[b])%M;
a=ff[a];
int c=gcd(a,b);
a/=c;
b/=c;
Extended_gcd(b,M);
x=(x+M)%M;
x=(x*a)%M;
return x;
}
//Lucas定理
int Combination(int n, int m)
{
int ans=1;
int a,b;
while(m||n)
{
a=n%M;
b=m%M;
n/=M;
m/=M;
ans=(ans*C(a,b))%M;
}
return ans;
}
int main()
{
int i,m,n;
ff[0]=1;
for(i=1; i<=M; i++) //预计算n!
ff[i]=(ff[i-1]*i)%M;
while(~scanf("%d%d",&n, &m))
{
printf("%d\n",Combination(n,m));
}
return 0;
}