组合数学+ybt题解

加法原理

乘法原理

排列数

\(n\) 个数中任取 \(m\) 个元素的排列的方案数,表示为 \(A^m_n=\frac{n!}{(n-m)!}\)

\(0!=1\)

全排列 \(A^n_n\)

组合数

\(n\) 个元素中取出 \(m\) 个元素的组合的个数,表示为 \(\dbinom{n}{m}= \frac{A^m_n}{m!}=\frac{n!}{m!(n-m)!}\)

如何理解呢?就是把取出来的排列方案数取缔为一个组合,对于取出来的排列的方案数有 \(m!\) 个,所以用全排列除以 \(m!\) 即可

特别的,规定 \(m>n\) 时,\(A^m_n=\dbinom{n}{m}=0\)

二项式定理

\((a+b)^n=\sum^n_{i=0}\dbinom{n}{i} a^{n-i}b^i\)

通过这个式子,我们可以求出展开式的系数,另外的,杨辉三角也遵循这个规律

证明

二项式定理表明,对于任何正整数\(n\)和任何实数\(a\)\(b\),都有以下等式成立:

\[(a + b)^n = \sum_{k=0}^{n} \binom{n}{k} a^{n-k} b^k \]

其中,\(\binom{n}{k}\)是组合数,表示从\(n\)个不同元素中取出\(k\)个元素的组合数。
下面使用数学归纳法来证明二项式定理:
基础步骤(n=0):
\(n=0\)时,等式左边为\((a + b)^0 = 1\),等式右边为\(\binom{0}{0} a^{0-0} b^0 = 1\),显然两边相等,基础情况成立。
归纳假设:
假设当\(n=m\)时,二项式定理成立,即

\[(a + b)^m = \sum_{k=0}^{m} \binom{m}{k} a^{m-k} b^k \]

归纳步骤:
我们需要证明当\(n=m+1\)时,二项式定理也成立。
考虑\((a + b)^{m+1}\),可以将其写成\((a + b)(a + b)^m\),根据归纳假设,我们有:

\[(a + b)^{m+1} = (a + b) \sum_{k=0}^{m} \binom{m}{k} a^{m-k} b^k \]

展开右边的乘积,我们得到:

\[= a \sum_{k=0}^{m} \binom{m}{k} a^{m-k} b^k + b \sum_{k=0}^{m} \binom{m}{k} a^{m-k} b^k \]

将两个求和式合并,我们可以重新排列和组合项:

\[= \sum_{k=0}^{m} \binom{m}{k} a^{m+1-k} b^k + \sum_{k=0}^{m} \binom{m}{k} a^{m-k} b^{k+1} \]

注意到第二个求和式中的项可以重新索引,将\(k+1\)替换为\(k\),得到:

\[= \sum_{k=0}^{m} \binom{m}{k} a^{m+1-k} b^k + \sum_{k=1}^{m+1} \binom{m}{k-1} a^{m+1-k} b^k \]

将两个求和式合并,并注意到当\(k=0\)时,第二个求和式中没有对应项,当\(k=m+1\)时,第一个求和式中没有对应项,因此我们可以将两个求和式合并为一个从\(k=0\)\(k=m+1\)的求和式:

\[= \sum_{k=0}^{m+1} \left( \binom{m}{k} + \binom{m}{k-1} \right) a^{m+1-k} b^k \]

利用组合数的性质\(\binom{m}{k} + \binom{m}{k-1} = \binom{m+1}{k}\),我们有:

\[= \sum_{k=0}^{m+1} \binom{m+1}{k} a^{m+1-k} b^k \]

这正是我们要证明的\(n=m+1\)时的二项式定理的形式。因此,归纳步骤成立。
由基础步骤和归纳步骤,我们可以得出结论,二项式定理对所有正整数\(n\)都成立。

组合数的递推



为什么这个公式成立呢? 考虑一种dp思想,如果我选第n个数,那么我有 \(C^{m-1}_{n-1}\) 种方案,若我不选n,那我有 \(C^m_{n-1}\) 种方案

贴图真好用(逃

组合数的性质


考虑一个n位的二进制数,能组成的数有 \(2^n\) 种,于是 \(C^r_n\) 就是n位中有r个1的数的个数

卢卡斯定理及证明

ps:一般 \(p\in [1e5,1e6]\) ,适合用卢卡斯求组合数

T1:

递推出阶乘,再用快速幂算乘法逆元和次方即可

T2:

卢卡斯定理板子题

我们现预处理出模数以内的阶乘,然后用卢卡斯定理递归到模数以内后求解

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e4,p=10007;
int t,n,m;
int fac[N],lg[60];
int quickpow(int x,int k){
    lg[0]=x;
    for(int i=1;i<=30;i++){
        lg[i]=lg[i-1]*lg[i-1]%p;
    }
    int res=1;
    for(int i=0;i<=30;i++){
        if(!((k>>i)&1))  continue;
        res=res*lg[i]%p;
    }
    return res;
}
int C(int n,int m){
    if(n<m)  return 0;
    if(m==0)  return 1;
    return fac[n]*quickpow(fac[m],p-2)%p*quickpow(fac[n-m],p-2)%p;
}
int lucas(int n,int m){
    if(n<m)  return 0;
    if(m==0)  return 1;
    return C(n%p,m%p)*lucas(n/p,m/p)%p;
}
signed main(){
    fac[0]=1;
    for(int i=1;i<=p;i++){
        fac[i]=fac[i-1]*i%p;
    }
    scanf("%lld",&t);
    while(t--){
        scanf("%lld%lld",&n,&m);
        printf("%lld\n",lucas(n,m));
    }
    return 0;
}

T3:

数论大杂烩!

还是很好理解的题解

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4,mod=999911659;
int n,g;
int sum[5],lg[60],fac[5][N];
int p[6]={0,2,3,4679,35617,999911659};
int quickpow(int x,int k,int md){
    lg[0]=x;
    for(int i=1;i<=40;i++){
        lg[i]=lg[i-1]*lg[i-1]%p[md];
    }
    int res=1;
    for(int i=0;i<=40;i++){
        if(!((k>>i)&1ll))  continue;
        res=res*lg[i]%p[md];
    }
    return res;
}
int fan(int x,int y,int md){
    return quickpow(x,y-2,md);
}  
int C(int n,int m,int md){
    if(n<m)  return 0;
    if(m==0)  return 1;
    return fac[md][n]*fan(fac[md][m],p[md],md)%p[md]*fan(fac[md][n-m],p[md],md)%p[md];
}
int lucas(int n,int m,int md){
    if(n<m)  return 0;
    if(m==0)  return 1;
    return C(n%p[md],m%p[md],md)*lucas(n/p[md],m/p[md],md)%p[md];
}
signed main(){
    for(int j=1;j<=4;j++){
        fac[j][0]=1;
        for(int i=1;i<=p[j];i++){
            fac[j][i]=fac[j][i-1]*i%p[j];
        }
    }
    scanf("%lld%lld",&n,&g);
    if(g%mod==0){
        printf("0\n");
        return 0;
    }
    for(int i=1;i*i<=n;i++){
        if(n%i!=0)  continue;
        for(int j=1;j<=4;j++){
            sum[j]+=lucas(n,i,j);
        }
        if(i*i==n)  continue;
        for(int j=1;j<=4;j++){
            sum[j]+=lucas(n,n/i,j);
        }
    }
    int ans=0;
    for(int i=1;i<=4;i++){
        int k=(mod-1)/p[i];
        int c=quickpow(k,p[i]-2,i);
        ans+=sum[i]*k*c;
        ans%=mod-1;
    }
    printf("%lld",quickpow(g,ans,5));
}

T4:

实际意义:有n个带编号的球,划分成标号连续的k段

然后考虑隔板法,答案就是 \(C^{k-1}_{n-1}\)

然后这题需要用到高精,懒得写了

T5:

把这个图形恒着切成两个矩形,然后枚举多少辆车在第一个矩形中,然后统计答案数即可

T6:

首先我们先把相同的数看成连续的一块

然后利用隔板问题,枚举可以分成 \(i\) 块,方案数即为 \(C^{i-1}_{n-1}\)

然后考虑在块里填数,方案数即为 \(2*C^{i}_{n}\),因为我们选出来这些数可以按照递增,也可以按照递减顺序排列

特殊情况: \(i=1\) 时,因为只有一种数,所以不存在递增递减,所以要特判

ps:预处理逆元可以将复杂度降到 \(O(n)\),详见代码

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,mod=2e6+3;
int n,ans;
int fac[N],inv[N],lg[50];
int quickpow(int x,int k){
    lg[0]=x;
    for(int i=1;i<=30;i++){
        lg[i]=lg[i-1]*lg[i-1]%mod;
    }
    int res=1;
    for(int i=0;i<=30;i++){
        if(!((k>>i)&1ll))  continue;
        res=lg[i]*res%mod;
    }
    return res;
}
int C(int b,int a){
    return fac[a]*inv[b]%mod*inv[a-b]%mod;
}
signed main(){
    scanf("%lld\n",&n);
    fac[0]=1;
    for(int i=1;i<=n;i++){
        fac[i]=fac[i-1]*i%mod;
    }
    inv[n]=quickpow(fac[n],mod-2);
    for(int i=n-1;i>=0;i--){
        inv[i]=inv[i+1]*(i+1)%mod;
    }
    ans=n;
    for(int i=2;i<=n;i++){
        ans+=2*C(i,n)%mod*C(i-1,n-1)%mod;
        ans%=mod;
    }
    printf("%lld\n",ans);
    return 0;
}

T3:

chat_GLM真好用(逃

然后

posted @ 2024-12-19 21:15  daydreamer_zcxnb  阅读(6)  评论(0编辑  收藏  举报