有 Cnm
下面是对四个不同数量级的处理
一、1 <= n , m <= 20
可以直接暴力阶乘求解
ll gcd(ll m,ll n){ //计算最大公约数
return n ? gcd(n,m%n) : m;
}
ll f(ll n)
{
ll ret=1;
for(ll i=1;i<=n;i++) ret*=i;
return ret;
}
ll C(ll n,ll m) //计算组合数
{
ll fz=f(n); //分子
ll fm=f(m)*f(n-m); //分母
ll z=gcd(fz,fm); //约分
ll ans=(fz/z)/(fm/z);
}
不难看出,里面有很多重复计算的值,比如 m! 。
所以我们还可以小小的优化一下。
优化点:
①根据组合数(Cnm)的性质,当 m > n/2 时,效果与 n-m 一致。
②分子、分母去重。
分子:n * (n-1) * (n-2) …… 3 * 2 * 1
分母:[ m * (m-1) … 2 * 1 ] * [ (n-m) * (n-m-1) … 2 * 1 ]
加粗表示可以约分
③在计算的过程中,也可以不断地除去能除的数。比如 2 3 ……
long long cnm(int n,int m)
{
long long s = 1;
int k = 1;
if(m > n/2)
m = n-m;
for(int i=n-m+1;i<=n;i++)
{
s *= (long long)i;
while(k<=m && s%k == 0)
{
s /= (long long)k;
k++;
}
}
return s;
}
【注意】仍只能计算小范围!
二、数量级:103
由高中数学知识可以知道,组合数实际上就是杨辉三角,那我们就可以用递推式。
#include <bits/stdc++.h>
#define mod 1000000007
using namespace std;
typedef long long ll;
const ll N=2005;
ll C[N][N];
void init()
{
for(ll i=0;i<N;i++)
for(ll j=0;j<=i;j++)
if(!j) C[i][j]=1;
else C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
int main()
{
init();
ll a,b;
cin>>a>>b;
cout<<C[a][b]<<endl;
return 0;
}
三、数量级:10^5
预处理阶乘 + 逆元
数量级已经超过 long long 的范围了,而且分数不能直接取模,所以我们采用逆元处理。
先用数组存储每一个的阶乘结果和逆元结果。
不开 infac[] 数组也可以,直接求就是了。(见下面被注释掉的代码)
AC代码
#include <bits/stdc++.h>
#include <iostream>
#define mod 1000000007
using namespace std;
typedef long long ll;
const ll N=1e5+5;
ll fac[N],infac[N];
ll fastpower(ll base,ll power)
{
ll ret=1;
while(power){
if(power&1) ret=ret*base%mod;
power>>=1;
base=base*base%mod;
}
return ret;
}
void init()
{
fac[0]=infac[0]=1;
for(ll i=1;i<N;i++){
fac[i]=fac[i-1]*i%mod; //阶乘
infac[i]=infac[i-1]*fastpower(i,mod-2)%mod; //逆元
//不想要 infac[] 可以把这行删掉
}
}
ll Cc(ll n,ll m){ //组合数
return fac[n]*infac[m]%mod*infac[n-m]%mod;
}
//ll Cc(ll n,ll m) //组合数{
// return fac[n]*fastpower(fac[m]*fac[n-m]%mod,mod-2)%mod;
//}
int main()
{
init();
int t;
scanf("%d",&t);
while(t--){
ll n,m,k,q;
scanf("%lld%lld%lld%lld",&n,&m,&k,&q);
if(k>n) printf("0\n");
else{
ll fz=Cc(n,k);
ll fm=Cc(n+m,k);
fz=fastpower(fz,q)%mod;
fm=fastpower(fm,q)%mod;
ll ans=(fz*fastpower(fm,mod-2))%mod;
printf("%lld\n",ans);
}
}
return 0;
}
四、数量级:10^18
这种数量级只能用卢卡斯定理
【证明】
一位大佬的证明,很简洁
也是大佬的,可以与上面补充使用
但适合测试组数较少的,否则容易超出题目的内存限制
#include <bits/stdc++.h>
#define mod 1000000007
using namespace std;
typedef long long ll;
ll fastpower(ll base,ll power)
{
ll ret=1;
while(power){
if(power&1) ret=ret*base%mod;
power>>=1;
base=base*base%mod;
}
return ret;
}
ll C(ll a,ll b)
{
if(b>a) return 0;
ll res=1;
for(ll i=1,j=a;i<=b;i++,j--){
res=res*j%mod;
res=res*fastpower(i,mod-2)%mod;
}
return res;
}
ll Lucas(ll a,ll b)
{
return C(a%mod,b%mod)*Lucas(a/mod,b/mod)%mod;
}
void sovle()
{
ll n,m,k,q;
cin>>n>>m>>k>>q;
ll fz=Lucas(n,k);
ll fm=Lucas(n+m,k);
fz=fastpower(fz,q);
fm=fastpower(fm,q);
ll ans=(fz*fastpower(fm,mod-2)%mod)%mod;
cout<<ans<<endl;
}
int main()
{
int n;
scanf("%d",&n);
while(n--) solve();
return 0;
}
欢迎指正修改。