排列组合学习笔记

排列与组合

定义

  • 组合数

n n n个不同元素中,任取 m ( m ≤ n ) m(m≤n) m(mn)个元素并成一组,叫做从 n n n个不同元素中取出 m m m个元素的一个组合;从 n n n个不同元素中取出 m ( m ≤ n ) m(m≤n) m(mn)个元素的所有组合的个数,叫做从 n n n个不同元素中取出 m m m个元素的组合数。

我们通常用大写字母 C C C表示,计算公式如下:
C n m = n ! ( n − m ) ! m ! C_n^m=\frac{n!}{(n-m)!m!} Cnm=(nm)!m!n!
C n m C_n^m Cnm也可以写作 ( n m ) \binom{n}{m} (mn) C ( n , m ) C(n,m) C(n,m)

  • 排列数

排列,一般地,从 n n n个不同元素中取出 m ( m ≤ n ) m(m≤n) mmn个元素,按照一定的顺序排成一列,叫做从 n n n个元素中取出m个元素的一个排列 ( p e r m u t a t i o n ) (permutation) (permutation)。特别地,当 m = n m=n m=n时,这个排列被称作全排列 ( a l l   p e r m u t a t i o n ) (all\ permutation) (all permutation)

我们通常用大写字母 P P P表示,计算公式如下:
P n m = n ! ( n − m ) ! P_n^m=\frac{n!}{(n-m)!} Pnm=(nm)!n!

P n m P_n^m Pnm也可写作 n P m _nP_m nPm或者 A n m A_n^m Anm

一些性质

  • 组合数

性质1. 当 m = 0 m=0 m=0或者 n = m n=m n=m时它的值为 1 1 1。特别的,当 m > n m>n m>n时它的值为 0 0 0。(这应该十分显然吧

性质2. C n m = C n n − m C_n^m=C_n^{n-m} Cnm=Cnnm,因为选 m m m个元素出来相当于你剩下 m m m个元素而选 n − m n-m nm个元素出来,所以方案数是一样的。

性质3. C n m = C n − 1 m + C m − 1 n − 1 C_n^m=C_{n-1}^m+C^{n-1}_{m-1} Cnm=Cn1m+Cm1n1,这个就是组合数的一个递推式,它也是著名的杨辉三角,简单证明如下:

n n n个元素里面选 m m m个元素有两种选法,第一种为在前 n − 1 n-1 n1个内选 m m m个,而不选第 n n n个,方案数为 C n − 1 m C_{n-1}^m Cn1m,或者在前 n − 1 n-1 n1个元素内,只选 m − 1 m-1 m1个,而第 m m m个选第 n n n个元素,方案数为 C n − 1 m − 1 × 1 C_{n-1}^{m-1}\times 1 Cn1m1×1,所以由加法原理得, n n n个元素里面选 m m m个元素的方案数为 C n − 1 m + C m − 1 n − 1 C_{n-1}^m+C^{n-1}_{m-1} Cn1m+Cm1n1

性质4. ( a + b ) n = ∑ m = 0 n C n m a n − m b m (a+b)^n=\sum\limits_{m=0}^{n}C_n^ma^{n-m}b^m (a+b)n=m=0nCnmanmbm,也就是二项式展开的系数。

性质5. C n m = C n m − 1 × n − m + 1 m C_n^m=C_n^{m-1}\times \frac{n-m+1}{m} Cnm=Cnm1×mnm+1,由定义式相除得 C n m C n m − 1 = n − m + 1 m \frac{C_n^m}{C_n^{m-1}}=\frac{n-m+1}{m} Cnm1Cnm=mnm+1

  • 排列数

性质1. P n n = n ! P_n^n=n! Pnn=n!,显然。

性质2. P n m = C n m × P m m P_n^m=C_n^m\times P_m^m Pnm=Cnm×Pmm,这里这个公式将组合与排列联系起来了,我们可以这样理解,排列就是先选 m m m个元素出来的方案数 C n m C_n^m Cnm,每种方案的 m m m个元素再进行排列方案数 P m m P_m^m Pmm,所以总方案数根据乘法原理得 C n m × P m m C_n^m\times P_m^m Cnm×Pmm

  • 补充
    有重复元素的全排列

元素个数无限制:重复排列 ( p e r m u t a t i o n w i t h r e p e t i t o n ) (permutationwith repetiton) (permutationwithrepetiton)是一种特殊的排列。从 n n n个不同元素中可重复地选取 m m m个元素。按照一定的顺序排成一列,称作从 n n n个元素中取 m m m个元素的可重复排列。当且仅当所取的元素相同,且元素的排列顺序也相同,则两个排列相同。由分步记数原理易知,从n个元素中取m个元素的可重复排列的不同排列数为 n m n^m nm

元素个数有限制:先有 k k k种元素,第 i i i种元素的个数为 n i n_i ni个,我们令 n = ∑ i = 1 k n i n=\sum_{i=1}^kn_i n=i=1kni。我们可以先对元素编号,使其变成无重复元素,那么总方案数为 n ! n! n!,根据乘法原理可知,我们令答案为 a n s ans ans,那么 n 1 ! × n 2 ! × n 3 ! ⋯ n k ! × a n s = n ! n_1!\times n_2!\times n_3!\cdots n_k!\times ans = n! n1!×n2!×n3!nk!×ans=n!,所以公式为 m u l t i P k k = n ! n 1 ! × n 2 ! ⋯ n k ! multiP_k^k=\frac{n!}{n_1!\times n_2!\cdots n_k!} multiPkk=n1!×n2!nk!n!

可重复选择组合:设第 i i i个元素选 x i x_i xi个,转化为求方程 x 1 + x 2 + ⋯ + x n = m x_1+x_2+\cdots +x_n=m x1+x2++xn=m的非负整数解的个数,我们转化一下,令 y i = x i + 1 y_i=x_i+1 yi=xi+1,那么原方程就等于求 y 1 + y 2 + y 3 ⋯ + y n = m + n y_1+y_2+y_3\cdots +y_n=m+n y1+y2+y3+yn=m+n的正整数解的个数,我们将其转化为 n + m n+m n+m 1 1 1,然后使用插板法,将其插入 n − 1 n-1 n1个板子,每个间隔里 1 1 1的个数便是一个 y i y_i yi的值,那么原问题转化为在 n + m − 1 n+m-1 n+m1个位置上放 n − 1 n-1 n1个板子的方案数,且板子之间至少有一个间隔,那么答案就显然为 C n + m − 1 n − 1 C_{n+m-1}^{n-1} Cn+m1n1,也就是 C n + m − 1 m C_{n+m-1}^{m} Cn+m1m


程序上的实现

  • 排列数

对于全排列,我们直接 O ( n ) O(n) O(n)求阶乘,取模或者高精即可。对于重复元素全排列,我们使用快速幂即可在 O ( l o g 2 m ) O(log_2m) O(log2m)时间内解决。

对于普通的排列,我们直接套用公式,预处理 n n n以内的阶乘,然后 n ! n! n! ( n − m ) ! (n-m)! (nm)!相除即可。若取模,求逆元相乘即可。

其余的情况求法类似。

  • 组合数

第一种. 对于求一个组合数我们按照公式模拟,相除或者乘逆元即可,复杂度 O ( m a x ( n , m ) ) O(max(n,m)) O(max(n,m))

第二种. 对于求 C 1 0 ∼ C n n C_1^0\sim C_n^n C10Cnn内的 n 2 n^2 n2个组合数,我们使用杨辉三角的递推式即可,复杂度 O ( n 2 ) O(n^2) O(n2)(其实跑不满,严格来说是 O ( n × ( n + 1 ) 2 ) O(\frac{n\times (n+1)}{2}) O(2n×(n+1))

code

C[0][0]=1;
for(int i=1;i<=n;i++){
	C[i][0]=C[i][1]=1;
	for(int j=1;j<i;j++){
	   C[i][j]=C[i-1][j]+C[i-1][j-1];
   }
} 

第三种. 对于求 C 1 0 ∼ C n n C_1^0\sim C_n^n C10Cnn内的 n 2 n^2 n2个组合数,我们预处理 n n n内的阶乘(若取模还要处理 n n n内阶乘的逆元),复杂度 O ( n ) O(n) O(n)(加上逆元 O ( l o g 2 p + n ) O(log_2p+n) O(log2p+n),其中 p p p为模数),每次求取时候用阶乘计算一下即可。

第四种. 对于取模的情况下,模数 p p p较小而 n , m n,m n,m十分大,我们用 L u c a s Lucas Lucas定理(若模数不为质数则使用 E x L u c a s ExLucas ExLucas定理扩展卢卡斯定理),公式 C n m = C n p m p × C n m o d &ThinSpace;&ThinSpace; p m m o d &ThinSpace;&ThinSpace; p C_n^m=C_{\frac{n}{p}}^{\frac{m}{p}}\times C_{n\mod p}^{m\mod p} Cnm=Cpnpm×Cnmodpmmodp。(具体证明与推导博主后期会另外总结_(¦3」∠)_)

lucas的code
luogu P3807
不预处理
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=1e5+10;
ll inv(ll a,ll b,ll mod){
  ll ans=1ll;
  for(;b;b>>=1,(a*=a)%=mod){
    if(b&1)(ans*=a)%=mod;
  }
  return ans;
}
ll calc(int a,int b,int p){
  if(a<=b) return a==b;
  if(b>a-b) b=a-b;
  ll ans=1ll,c1=1ll,c2=1ll;
  for(ll i=0;i<b;i++){
    c1=(c1*(a-i))%p;
    c2=(c2*(b-i))%p;
  }
  ans=(c1*inv(c2,p-2,p))%p;
  return ans;
}
ll lucas(int n,int m,int p){

  ll ans=1ll;
  for(;n&&m&&ans;n/=p,m/=p){
    ans=(ans*calc(n%p,m%p,p))%p;
  }
  return ans;
}
int T,n,m,p;
int main(){
  scanf("%d",&T);
  while(T--){
    scanf("%d%d%d",&n,&m,&p);
    printf("%lld\n",lucas(n+m,n,p));
  }
  return 0;
}
用时: 0ms / 内存: 1746KB
/*******************************************/
预处理版本
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=1e5+10;
ll ny[M],pow[M];
int n,m,p,T;
ll lucas(int a,int b){
    if(a<b) return 0;
    if(a<p) return pow[a]*ny[b]*ny[a-b]%p;
    return lucas(a/p,b/p)*lucas(a%p,b%p)%p;
}
int main()
{
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&n,&m,&p);
        ny[0]=ny[1]=pow[0]=pow[1]=1ll;
        for(int i=2;i<=n+m;i++) pow[i]=1ll*pow[i-1]*i%p;
        for(int i=2;i<=n+m;i++) ny[i]=1ll*(p-p/i)*ny[p%i]%p;
        for(int i=2;i<=n+m;i++) ny[i]=1ll*ny[i-1]*ny[i]%p;
        printf("%lld\n",lucas(n+m,m));
    }
    return 0;
}
用时: 76ms / 内存: 2558KB

下面给出扩展lucas的模板代码

分解质因数+中国剩余定理+lucas定理
luogu P4720
用时: 88ms / 内存: 1746KB
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long 
using namespace std;
ll exgcd(ll a,ll b,ll &x,ll &y){
    if(!b){x=1;y=0;return a;}
    else{ll now=exgcd(b,a%b,y,x);y-=x*(a/b);return now;}
}
ll fpow(ll a,ll b,ll mod){
    ll ans=1;
    for(;b;b>>=1,a=(a*a)%mod){
        if(b&1) ans=(ans*a)%mod;
    }
    return ans%mod;
}
ll inv(ll a,ll mod){
    if(!a) return 0;
    ll x=0,y=0;
    exgcd(a,mod,x,y);
    x=((x%mod)+mod)%mod;
    if(!x) x=mod;
    return x;
}
ll calc(ll n,ll pi,ll pk){
    if(!n) return 1;
    ll ans=1;
    if(n/pk){
        for(ll i=2;i<=pk;i++)
           if(i%pi) ans=(ans*i)%pk;
        ans=fpow(ans,n/pk,pk);   
    }
    for(ll i=2,up=n%pk;i<=up;i++)
        if(i%pi) ans=(ans*i)%pk;
    return ans*calc(n/pi,pi,pk)%pk;
}
ll C(ll n,ll m,ll mod,ll pi,ll pk){
    if(m>n) return 0;
    ll a=calc(n,pi,pk),b=calc(m,pi,pk),c=calc(n-m,pi,pk);
    ll k=0,ans;
    for(ll i=n;i;i/=pi) k+=i/pi;
    for(ll i=m;i;i/=pi) k-=i/pi;
    for(ll i=n-m;i;i/=pi) k-=i/pi;
    ans=a*inv(b,pk)%pk*inv(c,pk)%pk*fpow(pi,k,pk)%pk;
    return ans*(mod/pk)%mod*inv(mod/pk,pk)%mod;
}

ll crt(ll n,ll m,ll mod){
    ll ans=0;
    for(ll x=mod,i=2;i<=mod;i++){
        if(x%i==0){
            ll pk=1;
            while(x%i==0) pk*=i,x/=i;
            ans=(ans+C(n,m,mod,i,pk))%mod;
        }
    }
    return ans;
}

ll n,m,p;
int main(){
    scanf("%lld%lld%lld",&n,&m,&p);
    printf("%lld\n",crt(n,m,p)%p);
    return 0;
}

第五种. 对于只求 n n n一定的,而 m m m变化的,可以使用组合数的性质5递推即可,复杂度为 O ( n ) O(n) O(n),取模的话,预处理逆元即可,复杂度 O ( n + n ) O(n+n) O(n+n)

code

C(n,m)
C[1]=N;C[0]=1;
inv[1]=1;//逆元
for(long long i=2;i<min(N,Mod);i++) inv[i]=((Mod-Mod/i)*inv[Mod%i])%Mod;
for(long long i=2,j=N-1;i<=M;i++,j--)C[i]=C[i-1]*j%Mod*inv[i])%Mod;

预处理分数线上下两部分。

code

fac[0]=1;
for(long long i=2;i<=N;i++)fac[i]=fac[i-1]*i%Mod;
inv[N]=pow(fac[N],mod-2);
for(long long i=N-1;i>=1;i++)inv[i]=inv[i+1]*(i+1)%Mod;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VictoryCzt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值