这篇文章算是前一段时间组合数学学习的总结吧,总算是告一段落了。
目录
序章
知识点
排列
组合
常用公式和定理
常见数列及其性质
母函数
FFT&&FWT
经典习题
其实本来自己对这些知识已经总结了一部分(手写的),不过发现网上也都有,写的也都挺全面的,就懒省事搬了网上的一部分知识点的总结,当然对于一名Acmer来说只知道知识的话是远远不够的,因此最后也会给大家推荐一些好的题目。
下面的内容大多来自于这篇博客(ACM-组合数学完全总结(知识点+模板))
1.不可重排列数:
(若n和r都是整数,0<=r<=n)
2.可重排列数:从n个物品中可重复的取k个的排列数为:n^k
3.圆排列:(n个不同物品中取m个的圆排列 )
4.不尽相异元素全排列:5.多重集的排列(重点)
(1)设元素a1,a2,…,an互不相同,从无限多重集{∞a1,∞a2,…,∞*an}中取r个元素的排列数为n^r
(2)设元素a1,a2,…,an互不相同,从有限多重集{k1 * a1,k2 * a2,…,kn * an}中取r个元素的排列数为n^r,各k均大于等于r
(3)设元素a1,a2,…,an互不相同,从有限多重集{k1 * a1,k2 * a2,…,kn * an}中元素的全排列为[(k1+k2+…+kn)! / k1! * k2! * … * kn!]。
(4)设元素a1,a2,…,an互不相同,从有限多重集{k1 * a1,k2 * a2,…,kn * an}中取r个元素,至少存在一个ki< r时,排列数为 [r * (r!/k1! * k2! * … * kn!)]。
即指数型母函数G(x)=1+x/1!+x^2 /2!+…+x^ki/ki! 中 x^r的系数。
1.不可重组合数:
2.可重组合数:
3.不相邻组合
4.多重集的组合
(1)设元素a1,a2,…,an互不相同,从无限多重集{∞* a1,∞* a2,…,∞* an}中取r个元素的组合数为C(n+r-1,r)
(2)设元素a1,a2,…,an互不相同,从有限多重集{k1* a1,k2* a2,…,kn* an},各k均大于等于r
从中取r个元素的组合数为C(n+r-1,r)。
(3)设元素a1,a2,…,an互不相同,从有限多重集{k1* a1,k2* a2,…,kn* an}中取r个元素,至少存在一个ki < r时
令母函数G(x)=1+x+x^2 +…+x^ki ,i=1,2…n,G(x)中x^r的系数即为所求。
5.常用组合数公式
(1)C(n,m)=C(n-1,m)+C(n-1,m-1) ——常用于预处理递推求组合数
(2)C(n,m)=C(n,n-m)
(3)C(n,m+1)=(n-m)/(m+1)*C(n,m)6.组合数取模(如果数据比较大的话,可以Lucas定理(大组合数取模))
#include
using namespace std; typedef long long LL; const LL maxn(1000005), mod(1e9 + 7); LL Jc[maxn]; void calJc() //求maxn以内的数的阶乘 { Jc[0] = Jc[1] = 1; for(LL i = 2; i < maxn; i++) Jc[i] = Jc[i - 1] * i % mod; } /* //拓展欧几里得算法求逆元 void exgcd(LL a, LL b, LL &x, LL &y) //拓展欧几里得算法 { if(!b) x = 1, y = 0; else { exgcd(b, a % b, y, x); y -= x * (a / b); } } LL niYuan(LL a, LL b) //求a对b取模的逆元 { LL x, y; exgcd(a, b, x, y); return (x + b) % b; } */ //费马小定理求逆元 LL pow(LL a, LL n, LL p) //快速幂 a^n % p { LL ans = 1; while(n) { if(n & 1) ans = ans * a % p; a = a * a % p; n >>= 1; } return ans; } LL niYuan(LL a, LL b) //费马小定理求逆元 { return pow(a, b - 2, b); } LL C(LL a, LL b) //计算C(a, b) { return Jc[a] * niYuan(Jc[b], mod) % mod * niYuan(Jc[a - b], mod) % mod; } int main() { return 0; }
1.二项式定理:
2.鸽巢原理:将n+1个物品放到n个抽屉中,有一个抽屉至少有两个物品
3.常见恒等式:4.帕斯卡恒等式:
5.Lucas定理推论:C(n,m)为奇数 <=> n&m=m
6.容斥原理:
7.错排问题:
考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 n个元素的错排数记为D(n)简化公式:
递推公式:
Dn=(n-1)(Dn-1+Dn-2) n>3, D1 = 0 , D2 = 1
1.斐波那契数列:
1.1定义:
Fi=Fi-1+Fi-1,F1=F2=1的数列{Fn}称为斐波那契数列,Fn为斐波那契数
1.2通项公式:1.3特殊形式公式:
Fn ≡ 276601605(691504013^n -308495997^n)(mod (1e9+9)) 由二次剩余推导
1.4性质:
1.平方与前后项:从第二项开始,每个奇数项的平方都比前后两项之积少1,每个偶数项的平方都比前后两项之积多1
2.与集合子集:Fn+2同时也代表了集合{1,2,…,n}中所有不包含相邻正整数的子集个数
3.奇数项求和:F1+F3+F5+…+F(2n-1) = F(2n)-F2+F1
4.偶数项求和:F2+F4+F6+…+F(2n) = F(2n+1)-F1
5.平方求和:F1^2 +F2^2 +…+Fn^2 = Fn*F(n+1)
6.两倍项关系:F(2n)/Fn = F(n-1)+F(n+1)
7.F(n-1)*F(n+1)-Fn^2 = (-1)^n
8.质数数量:
每3个连续的数中有且只有一个被2整除
每4个连续的数中有且只有一个被3整除,
每5个连续的数中有且只有一个被5整除,
每6个连续的数中有且只有一个被8整除,
每7个连续的数中有且只有一个被13整除,
每8个连续的数中有且只有一个被21整除,
每9个连续的数中有且只有一个被34整除
9.尾数循环:
个位数每60步一循环,后两位数每300步一循环,后三位数,每1500步一循环,后4位数每15000步一循环,后5位数每150000步一循环
2.卡特兰数列:
2.1计算公式:
h(n)=C(2n,n)/(n+1)
h(n)=C(2n,n)-C(2n,n-1)
2.2递推公式:
h(n) = h(0)*h(n-1) + h(1)h(n-2) + … + h(n-1)h(0) (n>=2)
h(n)=h(n-1)((4n-2)/(n+1))
2.3常见用法(重点):
卡特兰数的应用都可以归结到一种情况:有两种操作,分别为操作一和操作二,它们的操作次数相同,都为 N,且在进行第 K 次操作二前必须先进行至少 K 次操作一,问有多少中情况?结果就Catalan(N)。
1.给定n个数,有多少种出栈序列
2.n个结点的二叉树有多少种构型
3.有n+1个叶子的满二叉树的个数
4.在nn的格子中,只在下三角行走,每次横或竖走一格,有多少种走法
5.将一个凸n+2边型剖分成三角形的方法数
6.在圆上选择2n个点,将这些点成对相连使得到的n条线段不相交的方法数
7.n个长方形填充一个高度为n的阶梯状图形的方法数
8.由n个+1和n个-1组成的排列中,满足前缀和>=0的排列数
9.括号化问题,左括号和右括号各有n个时,合法的括号表达式的种类
10.有n+1个数连乘,乘法顺序的种类
11.n位二进制中,有m个0,其余为1,有h[C(n,m)-C(n,m-1)]种
12.将有2n个元素的集合中的元素两两分为n个子集,若任意两个子集都不交叉,那么我们称此划分为一个不交叉划分。此时不交叉的划分数是Catalan(N)
13.n层的阶梯切割为n个矩形的切法数也是Catalan(N)。
14.在一个2n的格子中填入1到2n这些数值使得每个格子内的数值都比其右边和上边的所有数值都小的情况数也是Catalan(N)。求n<=35的卡特兰数
void init() { int i,j; LL h[36]; h[0]=h[1]=1; for(i=2;i<36;i++) { h[i]=0; for(j=0;j
求n>35的卡特兰数
//Lucas定理实现C(n,m)%p的代码:p为素数 LL exp_mod(LL a, LL b, LL p) { //快速幂取模 LL res = 1; while(b != 0) { if(b&1) res = (res * a) % p; a = (a*a) % p; b >>= 1; } return res; } LL Comb(LL a, LL b, LL p) { //求组合数C(a,b)%p if(a < b) return 0; if(a == b) return 1; if(b > a - b) b = a - b; LL ans = 1, ca = 1, cb = 1; for(LL i = 0; i < b; ++i) { ca = (ca * (a - i))%p; cb = (cb * (b - i))%p; } ans = (ca*exp_mod(cb, p - 2, p)) % p; return ans; } LL Lucas(LL n,LL m,LL p) { //Lucas定理求C(n,m)%p LL ans = 1; while(n&&m&&ans) { ans = (ans*Comb(n%p, m%p, p)) % p; n /= p; m /= p; } return ans; }
一篇讲的挺好的博客:http://www.wutianqi.com/blog/596.html
1.普通母函数:
普通母函数通常解决类似如下的问题:
给5张1元,4张2元,3张5元,要得到15元,有多少种组合?
某些时候会规定至少使用3张1元、1张2元、0张5元。
某些时候会规定有无数张1元、2元、5元。const int MAX=10000; const int INF=0x3f3f3f3f; //为计算结果,b为中间结果。 // K对应具体问题中物品的种类数。 //v[i]表示该乘积表达式第i个因子的权重,对应于具体问题的每个物品的价值或者权重。 //n1[i]表示该乘积表达式第i个因子的起始系数,对应于具体问题中的每个物品的最少个数,即最少要取多少个。0 //n2[i]表示该乘积表达式第i个因子的终止系数,对应于具体问题中的每个物品的最多个数,即最多要取多少个。INF //P为可能出现的最大指数(所求情况) int a[MAX],b[MAX],P; int k,v[MAX],n1[MAX],n2[MAX]; //初始化a void cal(int n) //n为种类数 { memset(a,0,sizeof(a)); a[0]=1; for (int i=1;i<=n;i++)//循环每个因子 { memset(b,0,sizeof(b)); for (int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++)//循环每个因子的每一项 for (int k=0;k+j*v[i]<=P;k++)//循环a的每个项 b[k+j*v[i]]+=a[k];//把结果加到对应位 memcpy(a,b,sizeof(b));//b赋值给a } }
2.指数型母函数
用于求多重排列数
如以下问题:
假设有8个元素,其中a1重复3次,a2重复2次,a3重复3次。从中取r个排列,求其排列数。
对于上面的问题“假设有8个元素,其中a1重复3次,a2重复2次,a3重复3次。从中取r个排列,求其排列数。”double c1[N],c2[N]; LL fac[N]; void Fac() //求阶乘 { fac[0]=1; for(int i=1;i<=N;i++) fac[i]=fac[i-1]*i; } int a[N]; //1~n每种的个数 void cal(int n,int m) //有n种,取m个 { memset(c1,0,sizeof(c1)); memset(c2,0,sizeof(c2)); c1[0]=1.0/fac[0]; for(int i=1;i<=n;i++) { for(int j=0;j<=m;j++) for(int k=0;k+j<=m && k<=a[i];k++) c2[k+j]+=c1[j]/fac[k]; for(int j=0;j<=m;j++) c1[j]=c2[j],c2[j]=0; } } ans=c1[m]*fac[m]; //取m个时的多重排列数
3.整数划分
1.有序拆分:把自然数n拆分成r个自然数之和,方案数有C(n-1,r-1)
2.无序拆分:把自然数n拆分成m个自然数之和,方案数有F(n,m)=F(n-1,m-1)+F(n-m,m),F(n,1)=F(n,n)=1#include
#include using namespace std; const int MAX=10000; const int INF=0x3f3f3f3f; //为计算结果,b为中间结果。 // K对应具体问题中物品的种类数。 //v[i]表示该乘积表达式第i个因子的权重,对应于具体问题的每个物品的价值或者权重。 //n1[i]表示该乘积表达式第i个因子的起始系数,对应于具体问题中的每个物品的最少个数,即最少要取多少个。 //n2[i]表示该乘积表达式第i个因子的终止系数,对应于具体问题中的每个物品的最多个数,即最多要取多少个。 //P为可能出现的最大指数(所求情况) int a[MAX],b[MAX],P; int k,v[MAX],n1[MAX],n2[MAX]; //初始化a void cal(int n) { memset(a,0,sizeof(a)); a[0]=1; for (int i=1;i<=n;i++)//循环每个因子 { memset(b,0,sizeof(b)); for (int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++)//循环每个因子的每一项 for (int k=0;k+j*v[i]<=P;k++)//循环a的每个项 b[k+j*v[i]]+=a[k];//把结果加到对应位 memcpy(a,b,sizeof(b));//b赋值给a } } int main() { int n; memset(n1,0,sizeof(n1)); memset(n2,INF,sizeof(n2)); for(int i=0;i<=125;i++) v[i]=i; while(cin>>n) { P=n; cal(n); cout<
其实这一方面我自己也不是特别的理解,fft还好,就是利用多项式表示方式的变换来快速(O(nlogn)的求两个多项式乘积的结果。fwt还没有来得及看,不过收藏了一些讲的不错的博客,放一下,以后遇见具体的题再补一下。
FWT:https://www.cnblogs.com/RabbitHu/p/9182047.html
FFT:https://blog.csdn.net/enjoy_pascal/article/details/81478582
排列组合和母函数:https://vjudge.net/contest/331970
容斥鸽巢原理:https://vjudge.net/contest/332707
都是我自己拉的专题。。