本篇博客来自南昌理工学院acm集训队成员yyj
组合数
1.定义
组合数:从 n 个不同元素中每次取出 m 个不同元素 ,不管其顺序合成一组,称为从 n 个元素中不重复地选取 m 个元素的一个组合。所有这样的组合的种数称为组合数。
2.性质与描述
2.1写法
在线性写法中被写作C(n,m)。
组合数的计算公式为:

讯享网
2.2性质
性质1.
从n个不同元素中取出m个元素的组合数 == 从n个不同元素中取出 (n-m) 个元素的组合数;
理解:
这个性质很容易理解,例如C(9,2)=C(9,7),即从9个元素里选择2个元素的方法与从9个元素里选择7个元素的方法是相等的。

性质2:
组合恒等式 :
若表示在 n 个物品中选取 m 个物品,则如存在下述公式:
C(n,m)=C(n,n-m)=C(n-1,m-1)+C(n-1,m)。
关于其数学证明可以看:这篇文章
dp证明:(帅哥美女必会)
如果学过一点点dp的帅哥美女都知道利用dp思想来解决问题
ex:
状态表示: c[i][j]表示在i个物品里选j个的选法总数; 那么选的状态:选这个物品 或者 不选 选第j个物品的总数为:c[i-1][j]; 不选第j个物品的总数为:c[i-1][j-1];(就是从前j-1个物品里选) 那么状态方程可以表示为: c[i][j]=c[i-1][j-1]+c[i-1][j];
讯享网
3.代码运用与解释:
3.1说明:
在学习算法的过程中注意数据范围是十分重要的,重要到就像有多少钱取什么样的老婆,在算法过程中数据范围越大,你的钱越少,挑老婆的余地也越少,那么使用的方法也就越难。接下来作者本人就带你学习一下。
3.2四大方法(有多少钱就选什么样的老婆):
(1):1<=b<=a<=2000,10万次询问。
解法:先预处理一下(递推),把所有答案都预处理出来,然后直接询问出答案。
处理方式:运用的组合恒等式 双重循环,dp求解。

代码如下:
讯享网// c[a][b] 表示从a个中选b个的方案数 for (int i = 0; i < N; i ++ ) for (int 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;
时间复杂度为:O(a^2);
(2).1<=a<=b<=,1万次询问,答案太大 需要mod 1e9+7。
解法:利用逆元求解
简单了解一下逆元:设x为a的逆元,那么有x*a(mod p)== 1;(a要和p互质)
x称为a模上p的逆元,那么x可以理解为a的倒数。因为乘起来结果取模是一样的。
逆元的求法:运用费马小定理:
若a与p互质那么:b^(p-1)mod§==1。
把b^(p-1)拆开来:b * b ^(p-2).
那么b ^(p-2)就相当于x了,即为b的逆元。
但是还是有很多细节需要注意,p可能太大了,需要快速幂来求解

看代码:
#include<iostream> using namespace std; const int N=1e5+10,mod=1e9+7;; long long f[N],nf[N]; int ksm(int a,int b,int p) //快速幂 {
long long res=1; while(b) {
if(b&1)res=res*a%p; a=(long long)a*a%p; b>>=1; } return res; } int main() {
f[0]=nf[0]=1; //0!=1,f[i]为i的阶层,nf为逆元 for(int i=1;i<=N;i++) {
f[i]=f[i-1]*i%mod; nf[i]=nf[i-1]*ksm(i,mod-2,mod)%mod; //快速幂求逆元 } int t; cin>>t; while(t--) {
int a,b; cin>>a>>b; printf("%lld\n",f[a]*nf[a-b]%mod*nf[b]%mod); } return 0; }
(3).1<=b<=a<=1e^18,1<=p<=1e5;
在此引入卢卡斯定理:
若p是质数,则对于任意整数 1 <= m <= n,有:
C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)
讯享网#include<iostream> using namespace std; long long ksm(long long a,long long b,long long p) {
long long res=1; while(b) {
if(b&1)res=res*a%p; a=a*a%p; b>>=1; } return res; } long long C(long long a,long long b,long long p) {
long long res=1; for(int i=1,j=a;i<=b;i++,j--) {
res=res*j%p; res=res*ksm(i,p-2,p)%p; //至于为什么,自己想。或者是看下面的解释 } return res; } long long lucas(long long a,long long b,long long p) {
if(a<p&&b<p)return C(a,b,p); //直接return else return C(a%p,b%p,p)*lucas(a/p,b/p,p)%p; //因为a/p不一定比p小,所以还是需要调用一次 } int main() {
int t; cin>>t; while(t--) {
long long a,b,p; cin>>a>>b>>p; cout<<lucas(a,b,p)<<endl; } }
为什么可以这样求解 :
Cba=a!(a−b)!∗b!=a∗(a−1)∗(a−2)∗…∗(a−b+1)∗(a−b)∗…∗1/(a−b)∗(a−b−1)∗…∗1∗b!
=a∗(a−1)∗(a−2)∗…(a−b+1)/b!
分子一共有a-(a-b)项,也就是b项。所以直接分别求阶层即可。
(4).1<=a<=b<=5000,不取模
当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用:
1. 筛法求出范围内的所有质数
2. 通过 C(a, b) = a! / b! / (a - b)! 这个公式求出每个质因子的次数。 n! 中p的次数是 n / p + n / p^2 + n / p^3 + …
3. 用高精度乘法将所有质因子相乘
#include<iostream> using namespace std; const int N=5010; bool str[N]; int prime[N],c[N],res[N],x; void muli(int b) //高精度 {
int t=0; for(int i=1;i<=x;i++) {
res[i]=res[i]*b+t; t=res[i]/10; res[i]=res[i]%10; } while(t) {
res[++x]=t%10; t=t/10; } while(res[x]==0&&x>0)x--; } int get(int a,int p) //求质因子的个数a!中质因子为p的个数 {
int t=0; while(a) {
t+=a/p; a/=p; } return t; } int main() {
int a,b; cin>>a>>b; for(int i=2;i<=a;i++) {
if(str[i]==0) for(int j=i+i;j<=a;j+=i)str[j]=true; 筛质数 } for(int i=2;i<=a;i++) {
if(!str[i]) {
c[i]=get(a,i); } } for(int i=2;i<=b;i++) {
if(!str[i]) {
c[i]-=get(b,i); // 减去b!的质因子 } } for(int i=2;i<=a-b;i++) {
if(!str[i]) {
c[i]-=get(a-b,i); // 减去(a-b)!的质因子 } } res[1]=1; x=1; //x为位数 for(int i=2;i<=a;i++) {
for(int j=1;j<=c[i];j++) {
muli(i); } } for(int i=x;i>=1;i--)printf("%d",res[i]); return 0; }
作者有话说:
基础打得好学到后面你自然学的就快,切莫狂妄自大,学习组合数运用到了很多知识点:
快速幂,高精度,筛法筛质数,逆元,费马小定理,卢卡斯定理,dp原理,只有拥有砸肆的基础才能走得更高。
话不多说,认真学习的帅哥有奖励:给你们看看漂亮小姐姐:

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/63442.html