求组合数的四种方法
求组合数
公式法
- 公式
- 公式理解
在b个要抽取的物品中任选一个x物品,第一次抽取情况无非两种,抽到了x和没有抽到x,如果抽到了x那么就只需要在剩下的a-1个中再抽取b-1个即可,如果没有抽到x,就在剩下的a-1个中抽取b个,两次相加即为抽取的总种数。 - 代码思路及实现
我们只需要将数据范围n内的所有组合数都预处理出来,就可以实现求任一个组合数。时间复杂度 ,本质是一个从1到n的等差数列求和。
给定n组数,每组包含一对a,b,求每一组
#include<iostream>
using namespace std;
const int N=2010;
long long c[N][N];
const int mod=1e9+7;
int main()
{
int n;
cin>>n;
for(int i=0;i<=2000;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;
}
}
}
while(n--)
{
int a,b;
scanf("%d %d",&a,&b);
printf("%ld\n",c[a][b]);
}
return 0;
}
- 代码细节
注意当b=0时的边界判定
定义法
- 算法思路
由组合数定义 ,这种结果一般会很大所以会要求模上一个数,所以我们算出a!后就不去算除法而是用逆元来算。 - 代码实现
当 如果 用公式法时间复杂度 会超时,而定义法时间复杂度为 不会超时。
#include<iostream>
using namespace std;
const int mod=1e9+7;
const int N=1e5+10;
typedef long long LL;
LL fant[N],infant[N];//fant[i]代表i的阶乘,infant[i]代表i的阶乘的逆元
LL quick_mod(int a,int b,int p)//这道题mod是一个质数所以可以用快速幂来求
{
LL res=1;
while(b)
{
if(b&1)
{
res=res*a%p;
}
b>>=1;
a=(LL)a*a%p;
}
return res;
}
int main()
{
int n;
cin>>n;
fant[0]=infant[0]=1;
for(int i=1;i<N;i++)
{
fant[i]=(LL)fant[i-1]*i%mod;
infant[i]=(LL)infant[i-1]*quick_mod(i,mod-2,mod)%mod;//第29行
}
while(n--)
{
int a,b;
scanf("%d %d",&a,&b);
printf("%lld\n",(LL)fant[a]*infant[b]%mod*infant[a-b]%mod);
}
return 0;
}
- 代码细节
第29行求逆元的过程本质上就是 ,即除以两个数同余先乘上一个数的逆元再乘上后一个数的逆元。
卢卡斯定理
- 定理
- 数学推导
要用到一个公式 ,这个公式的证明把左边展开即可: 因为从 到 都是p的倍数[1]模上一个p之后都为0,所以
将a和b用p进制表示为:
对于 有 ,由等式左边可以看出 的系数为 ,而 ,而从等式右边可以看出 的系数为 以此类推得 的系数 ,因为a,b是用p进制表示的则有: 实际意义为a在p进制下向右移一位, 实际意义为b在p进制下向右移一位。那么对 再进行一次上面的操作可以得到
综上有:
-代码实现
卢卡斯算法时间复杂度对于
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
int n;
int qmi(int a,int b,int p)
{
int res=1;
while(b)
{
if(b&1)
{
res=(LL)res*a%p;
}
b>>=1;
a=(LL)a*a%p;
}
return res;
}
int C(LL a,LL b,int p)
{
int res=1;
for(int i=1,j=a;i<=b;i++,j--)
{
res=(LL)res*j%p;
res=(LL)res*qmi(i,p-2,p)%p;
}
return res;
}
int lucas(LL a,LL b,int p)
{
if(b>a)
{
return 0;
}
if(a<p)
{
return C(a,b,p);
}
return (LL)C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;//a%p和b%p一定小于p所以直接用C来算组合数即可
}
int main()
{
cin>>n;
while(n--)
{
LL a,b;
int p;
scanf("%lld %lld %d",&a,&b,&p);
printf("%d\n",lucas(a,b,p));
}
return 0;
}
- 代码细节
当a,b都小于p时候才用C函数来算,其余都继续递归直到小于p。
大精度计算
- 针对类型
可计算a,b较大且结果不模上一个数的组合数 - 算法思路
对其分解质因数可得 对于任一个 它的次数都等于分子 所含 的个数减去分母 所含的 的个数,求一个阶乘的某个质因子的个数有公式: [2]一直加到分母比a大时停止(因为分母比a大时向下取整为0加了和没加一样),则结果为 - 实现步骤
先筛出2-a的所有质数,然后算出每个质数对应的次数,再算出 即可 - 代码实现
输入a,b求出
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long LL;
const int N=5010;
int primes[N];
int sum[N];
int cnt;
bool str[N];
int get(int x,int p)
{
int res=0;
while(x)
{
res+=x/p;
x/=p;
}
return res;
}
vector<int> mul(vector<int> a,int b)
{
vector<int> c;
int t=0;
for(int i=0;i<a.size();i++)
{
t+=a[i]*b;
c.push_back(t%10);
t/=10;
}
while(t)//这里得写成while,当b大于10的时候t在这里就可能大于10从而需要多次进位
{
c.push_back(t%10);
t/=10;
}
}
void get_primes(int n)
return c;
{
for(int i=2;i<=n;i++)
{
if(!str[i])
{
primes[cnt++]=i;
}
for(int j=0;i<=n/primes[j];j++)
{
str[i*primes[j]]=true;
if(i%primes[j]==0)
{
break;
}
}
}
}
int main()
{
int a,b;
cin>>a>>b;
get_primes(a);
for(int i=0;i<cnt;i++)
{
int p=primes[i];
sum[p]=get(a,p)-get(b,p)-get(a-b,p);
}
vector<int> res;
res.push_back(1);
for(int i=0;i<cnt;i++)
{
for(int j=0;j<sum[primes[i]];j++)
{
res=mul(res,primes[i]);
}
}
for(int i=res.size()-1;i>=0;i--)
{
printf("%d",res[i]);
}
return 0;
}
-代码细节
注意求大精度数相乘的模板
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析