求组合数问题
对于组合数问题,有时我们需要根据题目所给的数据范围来决定是否要预处理出来,下面给出几道例子及其分析
给定 n 组询问,每组询问给定两个整数 a,b请你输出 C(a,b)mod(10^9+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000,
1≤b≤a≤2000
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
这道题数据范围不是很大,我们可以直接通过C(m,n)=C(m-1,n-1)+C(m-1,n)预处理出来
下面是代码
#include<bits/stdc++.h> using namespace std; const int N=2002; const int mod=1e9+7; int c[N][N]; void init() { for(int i=1;i<N;i++) { c[i][0]=1;c[i][i]=1; for(int j=1;j<i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod; } } int main() { int n; cin>>n; init(); int a,b; while(n--) { cin>>a>>b; printf("%d\n",c[a][b]%mod); } return 0; }
给定 n组询问,每组询问给定两个整数 a,b,请你输出 C(a,b)mod(109+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000,
1≤b≤a≤10^5
输入样例:
3
3 1
5 3
2 2
输出样例:
3 10 1
这道题的数据用上面的组合公式预处理显然不太可能,这个时候我们可以按照组合数定义预处理
C(a,b)=a!/b!/(a-b)!
有这个公式可得C(a,b)%p=(a!)*(b!)^-1*(a-b)!^-1%p,逆元可以通过费马定理定理求得
下面给出代码
#include<bits/stdc++.h> using namespace std; const int N=100003; const long long mod=1e9+7; long long fact[N],infact[N]; //fact[i]记录i的阶乘模p的值,infact[i]记录i的阶程模p的逆元 typedef long long LL; LL qmi(LL a,LL b,LL p)//快速幂求逆元(只有互质情况下才能用) { LL ans=1; while(b) { if(b&1) ans=ans*a%p; b>>=1; a=a*a%p; } return ans; } void init() { fact[0]=infact[0]=1; for(int i=1;i<N;i++) { fact[i]=i*fact[i-1]%mod; infact[i]=qmi(fact[i],mod-2,mod)%mod; } } int main() { init(); int n; cin>>n; LL a,b; while(n--) { cin>>a>>b; printf("%lld\n",fact[a]*infact[a-b]%mod*infact[b]%mod); } return 0; }
下面是一个数据范围更大的例子:
给定 n组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 C(a,b)modp 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a,b,p。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤20,
1≤b≤a≤10^18,
1≤p≤10^5,
输入样例:
3
5 3 7
3 1 5
6 4 13
输出样例:
3
3
2
这道题运用的是lucas定理,分析在我前面的博客里面,有兴趣的小伙伴可以看一下!
#include<bits/stdc++.h> using namespace std; typedef long long LL; LL qmi(LL a,LL b,LL p) { LL ans=1; while(b) { if(b&1) ans=ans*a%p; b>>=1; a=a*a%p; } return ans; } LL C(LL a,LL b,LL p) { LL ans=1; for(int i=a,j=1;j<=b;i--,j++) ans=ans*i%p*qmi(j,p-2,p)%p; return ans; } LL lucas(LL a,LL b,LL p) { if(a<p&&b<p) return C(a,b,p); return C(a%p,b%p,p)*lucas(a/p,b/p,p)%p; } int main() { int n; cin>>n; LL a,b,p; while(n--) { cin>>a>>b>>p; printf("%lld\n",lucas(a,b,p)); } return 0; }
下面给出最后一道例子
输入 a,b,求 C(a,b) 的值。
注意结果可能很大,需要使用高精度计算。
输入格式
共一行,包含两个整数 a 和 b。
输出格式
共一行,输出 C(a,b) 的值。
数据范围
1≤b≤a≤5000
输入样例:
5 3
输出样例:
10
这道题只让求一个值,显然出题人的意图不是让我们预处理出来所有组合数的,但是我们也不能直接按照组合数定义来求,那样还会涉及到高精度除法,比较麻烦
我们可以将组合数质因数分解,并将每一个质因数的幂次都记录下来,最后直接用高精度计算结果即可!(不会高精乘的小伙伴可以看下我之前的博客,里面介绍了一些高精度运算方法)
下面是代码
#include<iostream> #include<vector> using namespace std; const int N=5002; int prime[N];//素数表 bool vis[N]; int fact[N];//记录质因素分解指数 int cnt; vector<int> ans; void init()//预处理出来所有小于N的质数 { for(int i=2;i<N;i++) { if(!vis[i]) prime[++cnt]=i; for(int j=1;j<=cnt&&i*prime[j]<N;j++) { vis[i*prime[j]]=true; if(i%prime[j]==0) break; } } } int get(int x,int t)//求x!中含有t的幂数和(值得记住方法) { int ans=0; while(x) { ans+=x/t; x/=t; } return ans; } vector<int> mul(vector<int> ans,int k)//高精乘 { int t=0; vector<int>c; for(int i=0;i<ans.size();i++) { t+=k*ans[i]; c.push_back(t%10); t/=10; } while(t) { c.push_back(t%10); t/=10; } return c; } int main() { init(); int a,b; cin>>a>>b; for(int i=1;prime[i]<=a;i++)//求出组合数的质因子分解式 fact[i]+=get(a,prime[i])-get(b,prime[i])-get(a-b,prime[i]); ans.push_back(1); for(int i=1;prime[i]<=a;i++) while(fact[i]--) ans=mul(ans,prime[i]); for(int i=ans.size()-1;i>=0;i--) printf("%d",ans[i]); return 0; }
希望大家能够喜欢!
心中所爱,山海可平