组合数
大前沿
对于排列数没什么好说,主要讲述组合数。
定义式和计算式
我们基本上简单的认定为我们从一个集合中不计顺序地取出不大于全集的集合的元素的方案数定义为组合数。
同时,有一个浅显易懂的实例代表组合数:我们从 \(n\) 个不同的数中取出 \(m\) 个数(我们不在意其选择的顺序) 的方案数。用 \(C_{n}^{m}\) 表示,但同时,用 \(\binom{n}{m}\) 来表示。
关于其计算,特殊情况特殊对待吧,对于上述的情况,则为 \(C_{n}^{m} = \frac{n!}{m!(n-m)!}\) 。
对于这个公式的一个浅显的理解 :
我们在选择第一个数的时候,有 \(n\) 种可能,在选择第 \(2\) 个数的时候,有 \(n-1\) 种可能,然后随着不断的选择,直到选择第 \(m\) 种情况,则为 \(n-m+1\) 种选择的可能。那么我们的这个数为 \(\prod_{i=n-m+1}^{n}i = \frac{n!}{(n-m)!}\) , 那么显然这是一个排列数,我们发现,这个组合数的会重复,所以应该再 \(/\) 一个 \(m!\)
一些性质
\(1.\) \(C_{n}^{m} = C_{n - 1}^{m} + C_{n-1}^{m-1}\)
显然这个式子我们是可以直接暴力计算是可行的,我们换一种理解方式,对于第 \(n\) 个元素,我们显然是有两种选择的,我可以选择第 \(n\) 个元素,也可以不选择,那么也就分别对应着 \(C_{n-1}^{m-1}\) 和 \(C_{n-1}^{m}\) 的两种情况,我们对于第 \(n\) 种元素的情况,加起来就好了。
\(2.\) \(C_{n+m}^{m}=C_{n+m}^{n}\)
对于左边的式子,我们可以简单的理解为,我们从 \(n+m\) 个数选择出 \(m\) 个来,对于右边的式子,则为我们选择 \(n\) 个扔掉,也就是留下 \(m\) 个,那不就和选择出 \(m\) 个来一样吗?,得证、
\(3.\) \(C_{n+r+1}^{r} = C_{n+r}^{r}+C_{n+r - 1}^{r-1} + \dots +C_{n,0}\)
我们根据定理 \(1\) ,我们可以将 \(C_{n+r+1}^{r}\) 分成 \(C_{n+r}^{r} ,C_{n+r}^{r-1}\) ,然后我们继续发现,我们可以拆掉后一项,就有 \(C_{n+r-1}^{r-1} , C_{n+r-1}^{r-2}\) , 最后分分分,就出来最上面的那个式子。
\(4.\) \(C_{n}^{l}C_{l}^{r} = C_{n}^{r}C_{n-r}^{l-r}\)
对于左边的式子,我们可以理解为我们先从 \(n\) 中选择 \(l\) 个元素出来,然后从 \(l\) 中选择 \(r\) 个元素扔掉。对于右边的式子,我们就可以理解为我们从 \(n\) 中选择 \(r\) 个,然后从 \(n-r\) 中选择 \(l-r\) 个元素,也就是,最后我们选择的是 \(n-l\) 个元素保留,扔掉 \(r\) , 我们发现和左边是一致的,那么这就说明,左右边式子是对等的。得证。
\(5.\) \(\sum_{i = 0}^{n} C_{n}^i = 2^n\)
我们考虑对于 \(n\) 个元素,我们有两种情况,选择或者不选择(因为这个式子可以代表着我随便选择 \(i\)个方案数总和),所以情况数应该为 \(2^n\)
\(6.\) \(C_{r}^{r} + C_{r+1}^{r} + …+C_{n}^r = C_{n+1}^{r}\)
和 \(3\) 的证明差不多,不赘述了。
\(7.\) \(C_{n}^{0} - C_{n}^1 + C_{n}^2 - … = 0\)
这个的证明,由于笔者实在是太菜了,没有证明,随便手模几组数据。
组合数的求解
递推求解
我们根据 \(C_{n}^{m} = C_{n-1}^m + C_{n-1}^{m-1}\) 这么个玩意,我们就能写出递推公式,我们发现这个递推公式和杨辉三角一样,那么我们就能够用杨辉三角来求解组合数。 \(1e3\) 的数据显然是可过的。
暴力求解
直接暴力计算 \(n| , (n-m)!,m!\)
Lucas 定理
这里只是现现身而已来
二项式定理
我们考虑一下 \((x+y)^4\) 展开,我们发现
那么,这个式子就显然能够理解了。
P3746 [六省联考2017]组合数问题
【description】:
求解 $$(\sum_{i=0}{\infty}C_{nk} ) \ \ mod \ \ p$$
【Solution】:
我们设 \(f_{n,r}=\sum_{i=0}^{\infty}C_{nk}^{ik+r}\)
然后我们化简一下设个 \(f_{n,r}\)
Lucas 定理
\(C_{n}^m \text{%}p \equiv C_{n / p}^{m/p}\times C_{n \text{%} p}^{m\text{%}p} \text{%} p\)
对于 \(p\) 为质数的时候成立。
证明,我不会。
Lucas 定理的小扩展
如果我们对 \(p\) 有一个限制条件为 \(p\) 不是质数,但是 \(p\) 一些质数的乘积,并且保证每个质因子的指数为 \(1\) , 那么怎么做呢? 我们假设 \(C_{n}^{m} = x\) , 那么我们就有好几个同余方程,如下:
其中 \(p_i\) 代表的是将 \(p\) 分解质因子得到的,因为其指数都是 \(1\) 次,那么我们就可以用中国剩余定理求解出 \(x\) 了。
Lucas 的大扩展
\(p\) 无条件,别太大就好。请自行学习。笔者在这里不做赘述。
给一个小题目
【description】
如何比较 \(C_{n_1}^{m_1}\) 与 \(C_{n_2}^{m_2}\) 的大小。\(C_{n_1}^{m_1},C_{n_2}^{m_2}\)很大,存不下就对了
【solution1】:
我们可以暴力求解计算,显然是不能取模的,这就很被动,因为我们根本存不了那么大,那么我们就可以考虑一下别的方法。
取对数 : 这么大,我们取个对数就能够比较了, \(log\) 显然是单调的,我们对 \(C_{n}^{m}\) 取个对数就好,那么我们来推导一下,取对数。
\(logC_{n}^{m} = log\frac{n!}{m!(n-m)!} = log(n!) - log(m!) - log((n-m)!)\)
然后我们直接给出一道题目;
【P4370 [Code+#4]组合数问题2】
【solution2】 :
这道题的做法,其实很显然,就是有一个地方很坑,就是 \(C_{n}^{m}\) 很大,存不下,这怎么办,根据上面的取对数的操作,我们直接对答案取模,然后,用 \(\log\) 去比较,就 \(OK\)
了。
- 流程:
- 我们先 \(n\) 个玩意逐个压进堆里去。
- 枚举 \(k\) 次,以此从堆里找大的用
【Code】
/*
By : Zmonarch
知识点:
*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
#define int long long
#define qwq register
#define inf 2147483647
const int kmaxn = 1e6 + 10 ;
const int kmod = 1e9 + 7 ;
inline int read() {
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = - 1 ; ch = getchar() ;}
while( isdigit(ch)) {x = x * 10 + ch - '0' ; ch = getchar() ;}
return x * f ;
}
struct Node{
int n , m ;
double val ;
bool operator < (const Node &b) const {return val < b.val ;}
};
std::priority_queue<Node> q ;
int n , k , ret ;
int jc[kmaxn] , inv[kmaxn] ;
double lg[kmaxn] ;
void init(int n) {
inv[1] = inv[0] = jc[0] = 1 ;
for(qwq int i = 1 ; i <= n ; i++) jc[i] = jc[i - 1] * i % kmod ;
for(qwq int i = 1 ; i <= n ; i++) lg[i] = lg[i - 1] + log(i) ;
for(qwq int i = 2 ; i <= n ; i++) inv[i] = ( - kmod / i + kmod) * inv[kmod % i] % kmod ;
for(qwq int i = 1 ; i <= n ; i++) inv[i] = inv[i - 1] * inv[i] % kmod ; // 我们要的是阶乘的逆元
}
signed main() {
init(kmaxn); n = read() , k = read() ;
for(qwq int i = 0 ; i <= n ; i++)
{
Node u ;
u.n = n ; u.m = i ; u.val = lg[n] - lg[i] - lg[n - i] ;
q.push(u) ;
}
while(k--)
{
Node u = q.top() ; q.pop() ;
ret = (ret + jc[u.n] * inv[u.m] % kmod * inv[u.n - u.m]) % kmod ;
//printf("%lld %lld %lf\n" , u.n , u.m , u.val) ;
Node nxt ;
nxt.n = u.n - 1 ; nxt.m = u.m ; nxt.val = (lg[nxt.n] - lg[nxt.m] - lg[nxt.n - nxt.m]) ;
q.push(nxt) ;
}
printf("%lld\n" , ret) ;
return 0 ;
}