组合数学+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\),都有以下等式成立:
其中,\(\binom{n}{k}\)是组合数,表示从\(n\)个不同元素中取出\(k\)个元素的组合数。
下面使用数学归纳法来证明二项式定理:
基础步骤(n=0):
当\(n=0\)时,等式左边为\((a + b)^0 = 1\),等式右边为\(\binom{0}{0} a^{0-0} b^0 = 1\),显然两边相等,基础情况成立。
归纳假设:
假设当\(n=m\)时,二项式定理成立,即
归纳步骤:
我们需要证明当\(n=m+1\)时,二项式定理也成立。
考虑\((a + b)^{m+1}\),可以将其写成\((a + b)(a + b)^m\),根据归纳假设,我们有:
展开右边的乘积,我们得到:
将两个求和式合并,我们可以重新排列和组合项:
注意到第二个求和式中的项可以重新索引,将\(k+1\)替换为\(k\),得到:
将两个求和式合并,并注意到当\(k=0\)时,第二个求和式中没有对应项,当\(k=m+1\)时,第一个求和式中没有对应项,因此我们可以将两个求和式合并为一个从\(k=0\)到\(k=m+1\)的求和式:
利用组合数的性质\(\binom{m}{k} + \binom{m}{k-1} = \binom{m+1}{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真好用(逃
然后