不同情境下组合数的求法
1、求组合数 I(直接预处理答案,用数组存)
给定n组询问,每组询问给定两个整数a,b,请你输出C(a,b) mod (1e9+7)的值。
输入格式
第一行包含整数n。
接下来n行,每行包含一组a和b。
输出格式
共n行,每行输出一个询问的解。
数据范围
1≤n≤10000, 1≤b≤a≤2000
做法:
有一万次询问,如果每次都算一遍,那么肯定会超时,再来看数据范围a和b都不大于2000,可以提前把所有的C(a,b)算出来,用数组存起来,这样每次询问的时候直接输出就好了
const int N = 2010, mod = 1e9+7;
int c[N][N];
void init()
{
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
if(j==0) c[i][j]=1;
else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
int main()
{
int t; cin>>t;
init();
while(t--)
{
int a,b;
cin>>a>>b;
cout<<c[a][b]<<endl;
}
return 0;
}
2、求组合数 II(预处理阶乘,用数组存,预处理答案数组存不下)
给定n组询问,每组询问给定两个整数a,b,请你输出Cba mod (1e9+7)的值。
输入格式
第一行包含整数n。
接下来n行,每行包含一组a和b。
输出格式
共n行,每行输出一个询问的解。
![](/qrcode.jpg)
数据范围
1≤n≤10000, 1≤b≤a≤1e5
做法:
与上一题的思路类似,也是先提前预处理出来,但是数组存不下这么大的范围,我们可以根据组合数原本的公式:c(n,m) = n! / ( (n-m)! m! ) ,把所有要用到的阶乘预处理出来,注意因为要求的是模上1e9+7的结果,并且计算式中有除法,这里不仅要算出所有的阶乘,也要算出所有阶乘的逆元,因为模数1e9+7是质数,直接用费马小定理算逆元即可
const int N = 1e5+5, mod = 1e9+7;
int fact[N],infact[N]; //阶乘 及 其逆元
int power(int a,int b,int p)
{
int res=1;
while(b) {
if(b&1) res=(ll)res*a%p; a=(ll)a*a%p; b>>=1; }
return res;
}
void init()
{
fact[0]=infact[0]=1;
for(int i=1;i<N;i++) fact[i]=(ll)fact[i-1]*i%mod;
for(int i=0;i<N;i++) infact[i]=power(fact[i],mod-2,mod);
}
int C(int n,int m)
{
int res=(ll)fact[n]*infact[n-m]%mod*infact[m]%mod; //根据定义式计算
return res;
}
int main()
{
int t; cin>>t;
init();
while(t--)
{
int a,b;
cin>>a>>b;
cout<<C(a,b)<<endl;
}
return 0;
}
3、求组合数 III(求非常大的数的阶乘,Lucas定理)
给定n组询问,每组询问给定三个整数a,b,p,其中p是质数,请你输出C(a,b)mod p的值。
输入格式
第一行包含整数n。
接下来n行,每行包含一组a,b,p。
输出格式
共n行,每行输出一个询问的解。
数据范围
1≤n≤20, 1≤b≤a≤1e18, 1≤p≤1e5
int p;
int power(int a,int b)
{
int res=1;
while(b) {
if(b&1) res=(ll)res*a%p; a=(ll)a*a%p; b>>=1; }
return res;
}
int C(int n,int m)
{
int res=1;
for(int i=1;i<=m;i++) res=(ll)res*(n-m+i)%p*power(i,p-2)%p; //除i变成乘i的逆元
return res;
}
int lucas(ll a,ll b)
{
if(a<p&&b<p) return C(a,b);
return (ll)C(a%p,b%p)*lucas(a/p,b/p)%p;
}
void solve()
{
ll a,b;
cin>>a>>b>>p;
cout<<lucas(a,b)<<endl;
}
int main()
{
int t; cin>>t;
while(t--) solve();
return 0;
}
4、求组合数 IV(用高精度求一个组合数,质数处理法)
输入a,b,求C(a,b)的值。
注意结果可能很大,需要使用高精度计算。
输入格式
共一行,包含两个整数a和b。
输出格式
共一行,输出C(a,b)的值。
数据范围
1≤b≤a≤5000
思路:
根据定义式:
但是要做一些处理,因为又要乘又要除不仅很麻烦,直接挨个乘除的效率也非常慢,所以可以把C(n,m) 提前分解成几个质数相乘的形式(任何一一个大于1的整数都能分解成有限个质数相乘的形式)
C(n,m) = P1a1P2a2……Pkak,这样只需要写一个高精度乘法就好了,并且调用该乘法函数的次数相对一开始也少了很多,效率很高
所以要先把质数筛选出来,求n!中质因子p的个数也是一个经典的算法,然后用高精度乘法一个一个乘就好了
const int N = 5010;
int cnt[N];
vector<int> prime;
bool vis[N];
void get_prime(int n) //线性筛
{
for(int i=2;i<=n;i++)
{
if(!vis[i]) prime.push_back(i);
for(int j=0;prime[j]<=n/i;j++)
{
vis[prime[j]*i]=1;
if(i%prime[j]==0) break;
}
}
}
int get(int n,int p) //得到n!中质因子p的个数
{
int cnt=0;
while(n) {
cnt+=n/p; n/=p; }
return cnt;
}
vector<int> mul(vector<int> &A,int b) //高精 乘 低精
{
vector<int> C;
for(int i=0,t=0;i<A.size()||t;i++)
{
if(i<A.size()) t+=A[i]*b;
C.push_back(t%10);
t/=10;
}
return C;
}
int main()
{
int a,b;
cin>>a>>b;
get_prime(a); //筛选质数
for(int i=0;i<prime.size();i++)
cnt[i]=get(a,prime[i])-get(a-b,prime[i])-get(b,prime[i]);
vector<int> res(1,1);
for(int i=0;i<prime.size();i++)
for(int j=0;j<cnt[i];j++)
res=mul(res,prime[i]);
for(int i=res.size()-1;~i;i--) cout<<res[i]; puts("");
return 0;
}