采用预处理方法,提前将所有的组合数都算出来,到时候直接查表
采用的公式是 C(a,b) = C(a-1,b) + C (a-1,b-1)
原题链接:885. 求组合数 I - AcWing题库
核心代码 :
for(int i=0;i<=2000;i++){
for(int j=0;j<=i;j++){
if(j==0)dp[i][j] = 1;
else dp[i][j] = (dp[i-1][j]+dp[i-1][j-1])%mod;
}
}
原题链接:886. 求组合数 II - AcWing题库
当a,b>=1e5时,显然已经不能直接开二维数组打表了,这样会爆数组
但是我们可以开两个一维数组,一个存取 i 的阶乘,一个存取 i 阶乘的逆元
我们可以直接从定义出发
C(a , b) = a! / b!/ (a-b)!
取模的时候,可以乘以逆元
所以C(a,b) = a! * b!^-1 * (a-b)!^-1
因为本题的m是质数,所以可以采用费马小定理。用快速幂求逆元
核心代码 :
for(int i=1;i<=N;i++){
ji[i] = ji[i-1]*i%mod; //求 i 的阶乘
ni[i] = qmi(ji[i],mod-2,mod)%mod; //求i的阶乘的逆元
}
cout<<ji[a]*ni[b]%mod*ni[a-b]%mod<<endl; //组合数定义公式
当a,b>1e9时,一维的数组也存不下了,所以打表也不可以了
可以采用 卢卡斯定理
即: C(a,b) = C(a%p , b%p)* C(a/p, b/p)
原题链接:887. 求组合数 III - AcWing题库
核心代码:
//快速幂求逆元
int qmi(int a,int k){
int res = 1;
while(k){
if(k&1) res = res*a%p;
a = a*a%p;
k>>=1;
}
return res;
}
//组合数定义 C(a,b) = a*(a-1)*(a-2)*...*(a-b+1) /b!
int C(int a,int b){
if (a<b) return 0;
int res = 1;
for(int i=a-b+1,j=1;i<=a;i++,j++){
res = res*i%p;
res = res*qmi(j,p-2)%p;
}
return res;
}
int luks(int a,int b){
if(a<p&&b<p)return C(a,b)%p;
else return C(a%p,b%p)%p*luks(a/p,b/p)%p;
}
当最终结果不取模的话,随便一个组合数都能爆long long ,所以此时只能用高精度来算
因为 组合数有除法,那样我们要写一个高精度乘法,一个高精度除法
但是我们可以优化掉除法
我们可以利用分解质因数来计算一个数字的阶乘,因为a>b,所以在a的阶乘中,一定包含着b的阶乘和(a-b)的阶乘的质因子,所以我们在只需要找到这些质因子,并且把对应的次数减掉就可以了。直接只需要高精度乘法即可
a在n中的次数怎么算呢
可以这么算,n/a +n/a^2 +n/a^3 直到a^k>=n停下
为什么这么做是正确的呢 举个例子
n = 25 找出5的次数 你会发现只有 5 10 15 20 25 => 15 2 * 5 3 5 4* 5 5* 5
那么 25/5+25/ 5^2 = 6;
25的阶乘中,5的次数是6
核心代码:
vector<int> mul(int x){
vector<int>a;
int r = 0;
for(int i=0;i<ans.size();i++){
r = r+ans[i]*x;
a.push_back(r%10);
r/=10;
}
while(r){
a.push_back(r%10);
r/=10;
}
return a;
}
void P(int n){
for(int i=2;i<=n;i++){
if(!st[i]){
st[i] = true;
prime[cnt++] = i;
}
for(int j=0;prime[j]<=n/i;j++){
st[prime[j]*i] = true;
if(i%prime[j]==0)break;
}
}
}
int getci(int p,int n){
int q = p;
int res = 0;
while(q<=n){
res+=(n/q);
q*=p;
}
return res;
}
直接上公式 卡特兰数 = C(2n,n)/(n+1)