[关闭]
@RiverHamster 2018-08-08T17:42:10.000000Z 字数 1963 阅读 1569

Day8:组合数学

Luogu 2018 SummerCamp

加法原理与乘法原理:

加法原理

如果为了达成目标有种方法,每种方法有种子方法,那么总共有

乘法原理

如果为了达成目标有步,每步有种实现方法,那么总共有

栗子:路线条数问题

一个有向图从起点走到终点到底有多少条路径?

可以用拓扑排序等方法解决。

排列与组合

排列

个人排成一排有多少种排法?

可以应用乘法原理,得出共有种方法

个人取个人,排成一排,共有多少种方法?

也应用乘法原理,得出有种。

这类 排列 问题可以被总结为

组合

组合数:从个元素中不考虑顺序地取个。

因为不考虑顺序,所以只需要在排列数的基础上除以全排列就好了

组合数也可以用dp方法求。

//杨辉三角 Pascal公式

时间复杂度是

常用方法

捆绑法

可以用的栗子

个人排成一排,其中有个人必须站在一起,有多少种方案?

两个人可以作为一个捆绑起来的元素,用个人进行排列,然后个人的顺序可以调换,所以最后的数量为

不能用的栗子

个人选个人,有个人必须一起选或一起不选,有多少种方案?

不能用捆绑法,因为捆绑后其他的都是个人,而捆绑的是个人,所以只能分类讨论:$$

隔板法

个人,但是有个人不能站在一起,那么有多少种方法?

先把个人排在一起,然后在个空位种插入个,方案数为

集合个元素,不相邻,求方案数

用隔板法,个点,插隔板,那么插板后编号就是一种方案。

预处理求组合数

我们消去,除法可以直接用求逆元的方法求。

预处理出阶乘数组a[]和逆元前缀积数组b[],那么

阶乘数组容易计算出,逆元数组可以用费马小定理求出,但是也可以线性递推:(是质数模数)

  1. inv[1] = 1; //取模的p一定要是质数
  2. for(int i=2; i<p; i++)
  3. inv[i] = (p-p/i)*inv[p%i]%p;

这样复杂度是的,可以背下来,证明可以在网上找,然后就可以预处理,求组合数。
由于逆元是积性的,我们可以先求出,然后递推:

  1. b[n] = Inv(n); //可以用快速幂或exgcd
  2. for(int i=n-1; i>=1; i--)
  3. b[i] = (b[i-1] * (i+1)) % p; //这种方法比之前的好记
  4. for(int i=n; i>=1; i--)
  5. inv[i] = b[i] * a[i-1]; //a[i] = i!

费马小定理求逆元要求模数必须是质数
求逆元只要两个数互质就可以了

二项式定理

的展开式是:
降幂排列,升幂排列
项添上系数

可以把个(x+a)展开,利用组合数理解。

栗子:

可以设,代入即可

栗子:

证明:组合数中为奇数的和等于为偶数的和

用二项式定理,代入即可。

栗子:P3414 组合数

直接套用二项式定理,输出即可。

二项式定理有各种形式和扩展,可以自行查阅。

杨辉三角

求杨辉三角中某一列一部分的和。

我们可以注意到,第列是第列的前缀和,然后我们只需要加上一个额外的元素,就可以直接利用一个元素算出前面的所有的和。

栗子:NOIP2016提高组 D2T1

组合数取模

给定,求,满足为质数,

只要不为,那么答案一定是

是质数,其他情况分子不可能约掉

  1. printf("%d\n", i==0||i==n?1:0);

Lucas定理

为质数时:

  1. int lucas(int n, int m, int p){ //p是质数
  2. if(m > n) return 0;
  3. if(n<=p && m<=p) return C(n, m);
  4. else return lucas(n/p, m/p) * lucas(n%p, m%p);
  5. }

可以将转成进制,转成进制,然后把每一位作组合数,最后乘起来,可以方便理解。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注