【学习笔记】数学知识-组合计数
加法原理(分类计数原理)
- 若完成一件事的方法有
类,其中第 类方法包括 种不同的方法,且这些方法互不重合,则完成这件事共有 种不同的方法。
乘法原理(分步计数原理)
- 若完成一件事的步骤有
个,其中第 个步骤包括 种不同的方法,且这些方法互不重合,则完成这件事共有 种不同的方法。
排列
- 从
个不同元素中依次取出 个不同的元素按照一定的顺序排成一列,产生的不同排列的数量称作排列数,记为 或 。递推式为 。- 相同的排列指元素相同且顺序相同。
- 当
时称作选排列;当 时称作全排列,由于 ,有 ;当 或 时,规定 。 - 通常情况下使用
代替 。
- 性质
- 应用
- 从
个不同元素可以重复地取出 个不同的元素按照一定的顺序排成一列,考虑顺序,产生的不同排列的数量为 ,称作相异元素的可重复选排列。 - 从
个不同元素依次取出 个不同的元素按照一定的顺序排成一个环形,考虑顺序,产生的不同环的数量为 ,称作相异元素的圆排列。- 相同的环指元素相同且在环上的顺序相同。
- 证明
- 当
时,考虑对于其中已经排好的一个环,从不同的位置断开,便可得到不同的排列,即 ,解得 。 - 当
时,先从 个元素中选出 个元素,再对选出的 个元素进行圆排列,即 。
- 当
- 例题
- 多重集是指包含重复元素的广义集合。设
是由 个 , 个 个 组成的多重集。则 的全排列个数为 。
- 从
- 例题
- luogu P5520 [yLOI2019] 青原樱
- luogu P3223 [HNOI2012] 排队
- 老师不相邻方案数
总方案数 老师相邻方案数 。 - 化简完,得
。
- 老师不相邻方案数
组合
- 从
个不同元素中依次取出 个不同的元素组成一个集合,不考虑顺序,产生的不同集合的数量称作组合数,记为 或 ,递推式为 。- 相同的组合指元素相同。
- 当
或 时,规定 。
- 性质
-
若
,则对于任意 均满足 。- 证明:首先由于
,有 为正整数,又因为 ,则 ,此时有 。
- 证明:首先由于
-
-
- 证明
- 将式子拆开即可,有
。
- 将式子拆开即可,有
- 证明
-
- 证明
- 将式子拆开即可,有
。 - 从二项式定理和杨辉三角的角度考虑,此结论是显然的。
- 将式子拆开即可,有
- 证明
-
- 证明
- 将式子拆开即可。
- 证明
-
- 证明:从二项式定理的角度考虑,取
的情况即可。
- 证明:从二项式定理的角度考虑,取
-
- 证明:从二项式定理的角度考虑,取
的情况即可。
- 证明:从二项式定理的角度考虑,取
-
- luogu P3414 SAC#1
- 证明
- 由于
,故 。
- 由于
-
- 证明
- 从组合意义的角度分析,此结论是显然的。
- 证明
-
-
- 证明详见 2.5 做题纪要 tgHZOJ 264. 选拔队员 。
-
- 证明
- 推式子,有
。
- 推式子,有
- 证明
-
- 组合数的求法
-
利用杨辉三角求解,用来解决给定
,多组询问,每次给定较小的 ,求 。-
luogu P2822 [NOIP2016 提高组] 组合数问题
点击查看代码
void C(ll n,ll p) { c[0][0]=c[1][0]=c[1][1]=1; for(ll i=2;i<=n;i++) { c[i][0]=1; for(ll j=1;j<=i;j++) { c[i][j]=(c[i-1][j-1]+c[i-1][j])%p; } } }
-
-
现算阶乘和阶乘的逆元,用来解决多组询问,每次给定
,求 ,常搭配 一起使用。点击查看代码
ll C(ll n,ll m,ll p) { ll a=1,b=1,i; if(n>=m&&n>=0&&m>=0) { for(i=n-m+1;i<=n;i++) { a=a*i%p; } for(i=1;i<=m;i++) { b=b*i%p; } return a*qpow(b,p-2,p)%p; } else { return 0; } }
-
预处理逆元,阶乘,阶乘的逆元,用来解决给定
,多组询问,每次询问给定 求 。-
点击查看代码
ll C(ll n,ll m,ll p) { return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p:0; } inv[1]=1; jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1; for(i=2;i<=N;i++) { inv[i]=(p-p/i)*inv[p%i]%p; jc[i]=jc[i-1]*i%p; jc_inv[i]=jc_inv[i-1]*inv[i]%p; }
-
-
给定较大数
,求 ,且需要高精度时。预处理素数,将分子、分母快速分解质因数,并保存各项质因子的指数,然后将分子、分母各个质因子的指数对应相减,即把分母消去,最后把剩余质因子乘起来。
-
- 应用
- 错位排列
-
错位排列是指没有任何元素出现在其有序位置的排列。即对于
的排列 ,如果不存在一个 满足 ,则称 是 的错位排列。记 表示 个元素的错位排列个数。递推式为 。特别的,当 时,有 。- 证明
- 当
或 时,显然。 - 当
且 时,首先,设把第 个元素放在第 个位置,一共有 种不同的方法。其次,第 个元素放到第 个位置时,一共有 种不同的方法;第 个元素不放到第 个位置时,一共有 种不同的方法。故有 。 - 当
时,设 ,有 ,移项得 。
- 当
- 证明
-
代码实现
点击查看代码
d[1]=0; d[2]=1; for(i=3;i<=n;i++) { d[i]=(i-1)*(d[i-1]+d[i-2]); } cout<<d[n]<<endl;
-
例题
- luogu P1595 信封问题
- UVA12024 Hats
- luogu P3182 [HAOI2016]放棋子
-
将障碍转化成错排挺显然的。
即为所求。点击查看代码
ll d[201][200],len[201],ans[200]; void jia(ll c[],ll &lenc,ll a[],ll &lena,ll b[],ll &lenb) { ll i,x=0; lenc=max(lena,lenb); for(i=1;i<=lenc;i++) { c[i]=a[i]+b[i]+x; if(c[i]>=1000000000000000) { x=c[i]/1000000000000000; c[i]%=1000000000000000; } else { x=0; } } lenc++; c[lenc]=x; while(lenc>0&&c[lenc]==0) { lenc--; } } void cheng(ll a[],ll &lena,ll b) { ll i,x=0; for(i=1;i<=lena;i++) { ans[i]=a[i]*b+x; if(ans[i]>=1000000000000000) { x=ans[i]/1000000000000000; ans[i]%=1000000000000000; } else { x=0; } } lena++; ans[lena]=x; while(lena>0&&ans[lena]==0) { lena--; } if(lena==0) { lena++; ans[lena]=1; } for(i=1;i<=lena;i++) { a[i]=ans[i]; } } int main() { ll n,i; cin>>n; len[1]=len[2]=1; d[1][1]=0; d[2][1]=1; for(i=3;i<=n;i++) { jia(d[i],len[i],d[i-1],len[i-1],d[i-2],len[i-2]); cheng(d[i],len[i],i-1); } cout<<d[n][len[n]]; for(i=len[n]-1;i>=1;i--) { printf("%015lld",d[n][i]); } return 0; }
-
- luogu P4071 [SDOI2016] 排列计数
-
即为所求。- 特别地,本题要求
。
点击查看代码
ll inv[1000001],jc[1000001],jc_inv[1000001],d[1000001]; ll C(ll n,ll m,ll p) { return (n>=m&&n>=0&&m>=0)?((jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p):0; } int main() { ll t,n,m,p=1000000007,i; scanf("%lld",&t); inv[1]=1; jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1; d[0]=1; d[1]=0; d[2]=1; for(i=3;i<=1000000;i++) { d[i]=((i-1)%p)*((d[i-1]+d[i-2])%p)%p; } for(i=2;i<=1000000;i++) { inv[i]=(p-p/i)*inv[p%i]%p; jc[i]=jc[i-1]*i%p; jc_inv[i]=jc_inv[i-1]*inv[i]%p; } for(i=1;i<=t;i++) { scanf("%lld%lld",&n,&m); printf("%lld\n",C(n,m,p)*d[n-m]%p); } return 0; }
- 特别地,本题要求
-
- CF340E Iahub and Permutations
-
- 设
是由 个 , 个 个 组成的多重集。设整数 ,则从 中选出 个元素组成一个多重集(不考虑元素顺序),产生的不同多重集的数量为 。 - 卡特兰数
- 错位排列
- 例题
- SP28304 ADATEAMS - Ada and Teams
- luogu P1771 方程的解
-
的正整数解的数量为 。- 等价于求把
个不同的小球分成 个部分,使得每个部分至少有一个小球的方案数。
- 等价于求把
-
的非负整数解的数量为 。- 等价于求
的正整数解的数量。
点击查看代码
ll num[1001],a[1001],ans[1001],lena=1; void cheng(ll a[],ll &lena,ll b) { ll i,x=0; for(i=1;i<=lena;i++) { ans[i]=a[i]*b+x; if(ans[i]>=1000000000000000) { x=ans[i]/1000000000000000; ans[i]%=1000000000000000; } else { x=0; } } lena++; ans[lena]=x; while(lena>0&&ans[lena]==0) { lena--; } if(lena==0) { lena++; ans[lena]=1; } for(i=1;i<=lena;i++) { a[i]=ans[i]; } } void divide(ll n,ll pd) { ll i,sum=0; for(i=2;i<=sqrt(n);i++) { if(n%i==0) { sum=0; while(n%i==0) { n/=i; sum++; } num[i]+=pd*sum; } } if(n>1) { num[n]+=pd; } } ll qpow(ll a,ll b,ll p) { ll ans=1; while(b>0) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } void C(ll n,ll m) { ll i,j; if(n>=m) { for(i=n-m+1;i<=n;i++) { divide(i,1); } for(i=1;i<=m;i++) { divide(i,-1); } for(i=1;i<=n;i++) { if(num[i]!=0) { for(j=1;j<=num[i];j++) { cheng(a,lena,i); } } } } } int main() { ll k,x,p=1000,i; cin>>k>>x; a[1]=1; C(qpow(x,x,p)-1,k-1); cout<<a[lena]; for(i=lena-1;i>=1;i--) { printf("%015lld",a[i]); } return 0; }
- 等价于求
-
- luogu P1350 车的放置
- 将棋盘分割为
和 的矩阵,因为 和 的矩阵放置的车有冲突情况,故 即为所求。
- 将棋盘分割为
- luogu P2606 [ZJOI2010] 排列计数
-
对于常见的计数
,记录是从哪个元素转移来的会耗费大量的空间,且无法确定是否合法。因此就需要利用离散化的思想来实现“等效代换”,记录其相对的大小关系。 -
容易发现满足性质的序列是一个小根堆。
-
设
表示以 为根的小根堆的大小, 表示以 为根的小根堆的合法方案数。 -
因为要求自己是最小的,故需要在
个中选出 个给左儿子,剩下的 个给有右儿子。 -
故得到转移方程
。最后, 即为所求。- 此时该小根堆内记录的不是实际元素,而是其之间的大小关系。
- 这里如果不理解,可以自己带几组样例模拟一下。
点击查看代码
ll inv[3000001],jc[3000001],jc_inv[3000001],f[3000001],s[3000001]; ll C(ll n,ll m,ll p) { if(n>=m&&n>=0&&m>=0) { return (jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p; } else { return 0; } } ll lucas(ll n,ll m,ll p) { return m?C(n%p,m%p,p)*lucas(n/p,m/p,p)%p:1; } int main() { ll n,p,i; cin>>n>>p; inv[1]=1; jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1; for(i=2;i<=min(n,p-1);i++) { inv[i]=(p-p/i)*inv[p%i]%p; jc[i]=jc[i-1]*i%p; jc_inv[i]=jc_inv[i-1]*inv[i]%p; } for(i=1;i<=2*n+1;i++) { f[i]=1; } for(i=n;i>=1;i--) { s[i]=s[i*2]+s[i*2+1]+1; f[i]=(lucas(s[i]-1,s[i*2],p)*f[i*2]%p)*f[i*2+1]%p; } cout<<f[1]<<endl; return 0; }
- 此时该小根堆内记录的不是实际元素,而是其之间的大小关系。
-
- luogu P6191 [USACO09FEB] Bulls And Cows S | HZOJ 720.种树
- 详见 普及模拟3 T4 种树 。
- LibreOJ 10235. 「一本通 6.6 练习 6」序列统计
-
记
,设 ,枚举长度 ,等价于求 的非负整数解的数量。 -
推式子,有
。点击查看代码
ll inv[1000010],jc[1000010],jc_inv[1000010]; ll C(ll n,ll m,ll p) { if(n>=m&&n>=0&&m>=0) { return (jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p; } else { return 0; } } ll lucas(ll n,ll m,ll p) { return m?C(n%p,m%p,p)*lucas(n/p,m/p,p)%p:1; } int main() { ll t,n,m,l,r,i,p=1000003; cin>>t; inv[1]=1; jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1; for(i=2;i<=p-1;i++) { inv[i]=(p-p/i)*inv[p%i]%p; jc[i]=jc[i-1]*i%p; jc_inv[i]=jc_inv[i-1]*inv[i]%p; } for(i=1;i<=t;i++) { cin>>n>>l>>r; m=r-l+1; cout<<(lucas(n+m,n,p)+p-1)%p<<endl; } return 0; }
-
- BZOJ3462 DZY Loves Math II
- luogu P2154 [SDOI2009] 虔诚的墓主人
-
观察到
极大,但 较小,于是考虑进行离散化。 -
对于横坐标相等,
的两棵常青树 ,其相隔的 个墓地对答案产生的贡献为 ,其中 分别表示 上、下、左、右方常青树的个数。 -
在枚举常青树的过程中即可处理。 -
考虑用单点修改、区间查询的树状数组维护
。注意枚举到一棵新的常青树时,要先减去其原来对答案的贡献,重新计算贡献。 -
细节较多。
点击查看代码
struct node { ll x,y; }a[100010]; ll x[100010],y[100010],c[100010][11],c2[100010][2],l[100010],sum[100010][2]; bool cmp(node a,node b) { return (a.x==b.x)?(a.y<b.y):(a.x<b.x); } ll query(ll a[],ll x) { return lower_bound(a+1,a+1+a[0],x)-a; } ll lowbit(ll x) { return (x&(-x)); } void add(ll n,ll x,ll key,ll p) { ll i; for(i=x;i<=n;i+=lowbit(i)) { c2[i][0]=(c2[i][0]+key+p)%p; } } ll getsum(ll x,ll p) { ll ans=0,i; for(i=x;i>0;i-=lowbit(i)) { ans=(ans+c2[i][0])%p; } return ans; } void C(ll n,ll m,ll p) { c[0][0]=c[1][0]=c[1][1]=1; for(ll i=2;i<=n;i++) { c[i][0]=1; for(ll j=1;j<=m;j++) { c[i][j]=(c[i-1][j-1]+c[i-1][j])%p; } } } int main() { ll n,m,w,k,u,d=0,r,lr,i,ans=0,p=2147483648; cin>>n>>m>>w; a[0].x=a[0].y=0; for(i=1;i<=w;i++) { cin>>a[i].x>>a[i].y; a[i].x++; a[i].y++; x[i]=a[i].x; y[i]=a[i].y; } cin>>k; sort(x+1,x+1+w); sort(y+1,y+1+w); x[0]=unique(x+1,x+1+w)-(x+1); y[0]=unique(y+1,y+1+w)-(y+1); C(w,k,p); for(i=1;i<=w;i++) { a[i].x=query(x,a[i].x); a[i].y=query(y,a[i].y); sum[a[i].x][0]++; sum[a[i].y][1]++; } sort(a+1,a+1+w,cmp); for(i=1;i<=w-1;i++) { d=((a[i-1].x==a[i].x)?d:0)+1; u=sum[a[i].x][0]-d; if(d>=k&&u>=k&&a[i].x==a[i+1].x) { lr=(a[i+1].y-1>=a[i].y+1)?(getsum(a[i+1].y-1,p)-getsum(a[i].y+1-1,p)+p)%p:0; ans=(ans+(((c[d][k]*c[u][k])%p)*lr)%p)%p; } add(y[0],a[i].y,-c2[a[i].y][1],p); l[a[i].y]++; r=sum[a[i].y][1]-l[a[i].y]; c2[a[i].y][1]=(l[a[i].y]>=k&&r>=k)?(c[l[a[i].y]][k]*c[r][k])%p:0; add(y[0],a[i].y,c2[a[i].y][1],p); } cout<<ans<<endl; return 0; }
-
- luogu P2467 [SDOI2010] 地精部落
-
设
表示长度为 的山脉且第一个位置是山谷的合法方案数, 表示长度为 的山脉且第一个位置是山峰的合法方案数。- 此时该山脉内记录的不是实际元素,而是其之间的大小关系。
-
容易有
从 转移过来的过程中,将 加入其中,一定是放在山峰的位置,故枚举插入的位置 ,从 个里面选出 个放在插入的 的左边,剩下的 个放在插入的 的右边。 同理。 -
故得到转移方程
。最终,有 即为所求。点击查看代码
ll c[2][4400],f[4400],g[4400]; int main() { ll n,p,i,j; cin>>n>>p; c[0][0]=c[1][0]=c[1][1]=1; f[0]=g[0]=f[1]=g[1]=1; for(i=2;i<=n;i++) { for(j=1;j<=i;j++) { c[i%2][0]=1; if(j%2==0) { f[i]=(f[i]+(((c[(i-1)%2][j-1]*f[j-1])%p)*f[(i-1)-(j-1)])%p)%p; } else { g[i]=(g[i]+(((c[(i-1)%2][j-1]*g[j-1])%p)*f[(i-1)-(j-1)])%p)%p; } c[i%2][j]=(c[(i-1)%2][j-1]+c[(i-1)%2][j])%p; } } cout<<(f[n]+g[n])%p<<endl; return 0; }
-
- luogu P4910 帕秋莉的手环
- luogu P3214 [HNOI2011] 卡农
二项式定理、二项式反演
- 二项式定理
- 二项式定理:
。 - 证明
- 数学归纳法
- 当
时, 成立。 - 假设当
时命题成立,当 时,有 。 - 证毕。
- 当
- 组合意义
- 将
展开时可以理解为从 个式子 中的 个里取 ,剩下的 个式子 里取 ,组成一个乘积项 ,其系数为 。
- 将
- 数学归纳法
- 例题
- 二项式定理:
- 二项式反演
- 设
表示恰好 个元素组成特定结构的方案数, 表示从 个元素中选出 个元素组成特定结构(虽然其他元素可能一并组成特定结构,但我们不去管它)的总方案数,即 ,那么有 。 - 证明
- 推式子,有
- 推式子,有
- 其他形式
- 设
表示 个元素中恰好有 个元素满足特定条件的方案数, 表示 个元素中至少有 个元素满足特定条件的方案数,即 ,那么有 。
- 设
- 例题
- 设
卢卡斯定理
-
卢卡斯定理:若
是质数,则对于任意正整数 ,有 ,其中对于每一个 均满足 ,则 。- 另一种写法为
.
- 另一种写法为
-
证明
- 考虑对于多项式
,展开后 的系数为 。 - 此时有
。 - 据
进制数的性质,将同余右边展开后有 的系数为 。 - 证毕。
- 考虑对于多项式
-
性质
为奇数当且仅当在二进制表示下 的每一个数位的数都不小于 的相应数位的数,即 。
-
代码实现
点击查看代码
ll lucas(ll n,ll m,ll p) { return (n>=m&&n>=0&&m>=0)?(m?C(n%p,m%p,p)*lucas(n/p,m/p,p)%p:1):0; }
-
例题
扩展卢卡斯定理
容斥原理
- 容斥原理:设
,则有 。 - 应用
- 设
是由 个 , 个 个 组成的多重集。设整数 ,则从 中选出 个元素组成一个多重集(不考虑元素顺序),产生的不同多重集的数量为 ,合并得 。- 证明
- 等价于求
的不同方案数。 - 正难则反,考虑合法方案数等于总方案数减不合法方案数。
- 总方案数同 luogu P1771 方程的解 ,即
。 - 难点在于如何求不合法方案数。设
表示至少包含 个 的多重集,则 的方案数为 。接着容斥原理,得出 ,其方案数为 。 - 二者相减即可得到原式。
- 等价于求
- 证明
- 设
- 例题
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/17824983.html,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)