ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[数学基础] 1 组合数

2022-05-09 23:35:28  阅读:182  来源: 互联网

标签:limits 组合 int sum 基础 times 数学 equiv mod


组合数

1. 求组合数

根据不同的数据范围,求组合数也可以运用不同的方法。由于这是中学的内容,所以这里就不详细介绍了。
求解的总的式子:

\(C_a^b=\frac{a!}{b!(a-b)!}\)

表示从\(a\)个物品中选出\(b\)个的方案数。

(1) 递推法

使用递推式\(C_a^b=C_{a-1}^b+C_{a-1}^{b-1}\)

证明:考虑已经得知了\(C_{a-1}^k,k\in[0,b]\)的结果,那么当前有\(a\)个物品时,第\(a\)个物品要么被选,要么不被选中。若被选中,则方案一共有\(C_{a-1}^{b-1}\)个,若不被选中,则方案有\(C_{a-1}^b\)个,方案累加,得证。

时间复杂度\(O(n^2)\)

void init(){
    for (int i=0;i<N;++i){
        for (int j=0;j<=i;++j){
            if (!j) C[i][j] = 1;
            else{
                C[i][j] = (C[i-1][j-1] + C[i-1][j]) % M;
            }
        }
    }
}
(2) 通过预处理逆元的方式求组合数

直接求出\(a!,\frac{1}{b!},\frac{1}{(a-b)!}\),然后再相乘。

时间复杂度\(O(n\log n)\)

int n;
const int N = 1e5 + 5;
const ll M = 1e9 + 7;
ll fac[N], invf[N];// 阶乘 阶乘的逆元

void init(){
    fac[0] = invf[0] = 1LL;
    for (int i=1;i<N;++i){
        fac[i] = fac[i-1] * i % M;
        invf[i] = invf[i-1] * qmi(i, M-2, M) % M;
    }
}

int main(void){
    scanf("%d", &n);
    init();
    ll a, b;
    for (int i=1;i<=n;++i){
        scanf("%lld%lld", &a, &b);
        ll ans = fac[a] * invf[b] % M * invf[a - b] % M;
        printf("%lld\n", ans);
    }
    
    return 0;
}
(3) 分解质因数法求组合数(高精度)

首先,可以把\(a!\)写成\(a!=p_1^{a_1}\times p_2^{a_2}\times ...\times p_k^{a_k}\)的形式,那么\(C_a^b\)的答案肯定可以写成\(C_a^b=p_1^{b_1}\times p_2^{b_2}\times ...\times p_k^{b_k},\forall i\in[1,k],b_i\leq a_i\)。

因此,设\(s_i\)为\(a!\)中包含的质因子\(p_i\)的个数减去\(b!,(a-b)!\)中包含的\(p_i\)的个数,则\(C_a^b=\prod_{i=1}^{k} p_i^{s_i}\)。

那么,\(a!\)包含的质因子个数为多少呢?答案是\(\lfloor\frac{a}{p}\rfloor+\lfloor\frac{a}{p^2}\rfloor+\lfloor\frac{a}{p^3}\rfloor...\)。

  • 代码:
const int N = 5000 + 5;
int p[N], sa[N], cnt;
bool st[N];

void Euler(int n){
	for (int i=2;i<=n;++i){
		if (!st[i]) p[++cnt] = i;
		for (int j=1;p[j]<=n/i;++j){
			st[p[j] * i] = true;
			if (i % p[j] == 0) break;
		}
	}
}

int get(int n, int p){
	int res = 0;
	while (n){
		res += n/p;
		n/=p;
	}
	return res;
}

vector<int> mul(vector<int>& A, int b){
    vector<int> res; int t = 0;
    for (int i=0;i<A.size();++i){
        t += A[i]*b;
        res.push_back(t % 10), t /= 10;
    }
    while (t){ res.push_back(t%10), t /= 10;}
    while (res.back()==0 && res.size()>1) res.pop_back();
    return res;
}

int main(void){
	int a, b;
	scanf("%d%d", &a, &b);
	Euler(a);
	for (int i=1;i<=cnt;++i){
		int cur = p[i];
		sa[i] = get(a, cur) - get(a-b, cur) - get(b, cur);
	}
	
	vector<int> res;
	res.push_back(1);
	for (int i=1;i<=cnt;++i){
		for (int j=0;j<sa[i];++j){
			res = mul(res, p[i]);
		}
	}

	for (int i=res.size()-1;i>=0;--i){
		printf("%d", res[i]);
	}
	puts("");
	
	return 0;
}
(4) Lucas定理求组合数
  • Lucas定理:\(C_a^b \equiv C_{a\mod p}^{b \mod p}\times C_{a/p}^{b/p} (\mod p)\)

  • 代码

其中,组合数直接通过公式\(C_a^b=\frac{a!}{b!(a-b)!}=\frac{a\times (a-1)\times...\times (a-b+1)}{b!}\)求得

ll C(ll a, ll b, ll p){
	if (b > a) return 0;
	ll res = 1;
	for (int i=1, j=a;i<=b;++i, --j){
		res = res * j % p; // 分子
		res = res * qmi(i, p-2, p) % p; // 分母
	}
	return res;
}

ll lucas(ll a, ll b, ll p){
	if (a<p && b<p) return C(a, b, p);
	return C(a%p, b%p, p) * lucas(a/p, b/p, p) % p;
}
(5) 取对数求组合数(可能有浮点误差)

此种方法大概可以计算1000以内的组合数。

先假设\(m\leq \frac{n}{2}\)吧,否则因为\(C(n,m)=C(n,m-n)\),令\(m=m-n\)。

则\(x=\ln C(n,m)=\ln (\frac{n!}{m!(n-m)!})=\sum\limits _{i=1}^n\ln i-\sum\limits _{i=1}^m\ln i-\sum\limits _{i=1}^{n-m}\ln i\)

这样就可以直接计算前缀和\(\ln n\),则\(C(n,m)=e^x\)。

2. 常用公式

(1) 二项式定理

\[(x+y)^n=\left(\begin{array}{} n\\ 0 \end{array}\right)x^ny^0+\left(\begin{array}{} n\\ 1 \end{array}\right)x^{n-1}y^1+...+\left(\begin{array}{} n\\ n-1 \end{array}\right)x^1y^{n-1}+\left(\begin{array}{} n\\ n \end{array}\right)x^0y^n \]

也可写成\((x+y)^n=\sum\limits_{k=0}^n\left(\begin{array}{} n\\ k \end{array}\right)x^{n-k}y^k=\sum\limits_{k=0}^n\left(\begin{array}{} n\\ k \end{array}\right)x^{k}y^{n-k}\)

其中,\(\left(\begin{array}{} n\\ k \end{array}\right)=C_n^k=\frac{n!}{k!(n-k)!}\)。

二项式定理的一个常用形式为\((1+x)^n=\sum\limits_{k=0}^n C_n^kx^k\)。

那么很明显,\((1-x)^n=\sum\limits_{k=0}^n (-1)^kC_n^kx^k\)

(2) 其他的一些小公式

\(C_n^0+C_n^1+...+C_n^n=2^n\)(从\(n\)取任意个数,显然二进制取或不取)

\(C_a^b=C_{a-1}^b+C_{a-1}^{b-1}\)(类似DP思想,对于第\(b\)个数,取或不取)

\(C_{m+n}^n=C_m^0C_n^n+C_m^1C_n^{n-1}+...+C_m^nC_n^0\)(从\(m\)个数取\(n\)个,\(n\)个取0个开始遍历)

\(C_n^m=C_n^{n-m}\)(显然从\(n\)中取\(m\)个的方案数和从\(n\)中取\(n-m\)个的方案数是一样的)

(这些递推式还是挺容易证明的就不写了,敲公式好累)

(2) 乘法原理和加法原理:类类独立,步步相关
  1. 加法原理:做一件事情,完成它可以有\(n\)类办法,在第一类办法中有\(m_1\)种不同的方法,在第二类办法中有\(m_2\)种不同的方法,……,在第\(n\)类办法中有\(m_n\)种不同的方法。那么完成这件事共有\(N=\sum_{i=1}^n m_i\)种不同的方法。

  2. 乘法原理:做一件事情,完成它需要分成n个步骤,做第一步有\(m_1\)种不同的方法,做第二步有m2种不同的方法,……,做第\(n\)步有种\(m_n\)不同的方法,那么完成这件事有\(N=\prod_{i=1}^n m_i\)种不同的方法。

(3) 可重复元素的组合问题

从n个元素中,可重复的挑选m个元素组成集合,求:不同的集合有多少个?

答案:\(C_{n+m-1}^m\)

证明:隔板法。

∣∗∣∗∗∗∗∣∣∗∗∗∣∣∗∣(|是边界,*是球)

假设这里有n个盒子,m个球,如果有k个球被放在了第i个盒子里,那么就相当于第i个数被选了k次。那么去掉两端边界,就相当于摆放n-1个盒子的边界和m个球有多少种摆放方式。从里面选择r个位置放球 剩余的都是盒子边界,因此答案就是\(C_{n+m-1}^m\)。

这里有位老哥用dp在\(O(n^2)\)搞定了,俺觉得也可以一看wwww,部分dp表真是一个好东西啊。

https://blog.csdn.net/m0_37602827/article/details/100624871

3. 常用定理

(1) Lucas定理

结论:\(C_a^b \equiv C_{a\mod p}^{b \mod p}\times C_{a/p}^{b/p} (\mod p)\) (\(p\)为素数)

它还可以写成如下形式:

若\(p\)为素数,则\(n\)的\(p\)进制表示为\((a_k,a_{k-1},...,a_0)\),\(m\)的\(p\)进制表示为\((b_k,b_{k-1},...,b_0)\)

则\(C_n^m\equiv C_{a_k}^{b_k}·C_{a_{k-1}}^{b_{k-1}}...C_{a_0}^{b_0} (\mod p)\)

很容易看出来,它就是形式一运用数学归纳法可以得出的结论。

证明

引理1 \(C(p,x) \equiv 0(\mod p), 0<x<p\)

证明:$C(p,x)\equiv \frac{p!}{x!(p-x)!}\equiv \frac{p\times (p-1)!}{x\times(x-1)\times(p-x)!} $

由于\(p\)为素数,所以\(\forall x\in(0,p),(p,x)=1\),故\(x|C_{p-1}^{x-1}\),则\(p|(\frac{p}{x}C_{p-1}^{x-1})\)。

所以\(C(p,x) \equiv p\times(x^{-1} \mod p)\times C(p-1,x-1)\equiv 0\mod p\)

引理2 \((1+x)^p \equiv (1+x^p) \mod p\)

根据二项式定理可得\((1+x)^p=\sum\limits _{k=0}^pC_p^kx^k\)

由引理1可得\(\sum\limits _{k=0}^pC_p^kx^k\equiv C(p,0)x^0+C(p,p)x^p\equiv (1+x^p) \mod p\)

下面正式推导Lucas定理。

假设\(n=q_1p+r_1,m=q_2p+r_2\),则\(q_1=n/p,q_2=m/p\)

\((1+x)^n\equiv (1+x)^{q_1p+r_1}\equiv (1+x)^{q_1p}\times(1+x)^{r_1}\equiv [(1+x)^p]^{q_1}\times(1+x)^{r_1} \\ (1+x^p)^{q_1}\times(1+x)^{r_1} \equiv \sum\limits _{i=0}^{q_1}C(q_1,i)x^{p\times i}\times \sum\limits _{j=0}^{r_1}C(r_1,j)x^{j} (\mod p)\)

又\((1+x)^n=\sum\limits _{i=0}^{n}C(n,i)x^{i}\)

\(\therefore \sum\limits _{i=0}^{n}C(n,i)x^{i}\equiv \sum\limits _{i=0}^{q_1}C(q_1,i)x^{p\times i}\times \sum\limits _{j=0}^{r_1}C(r_1,j)x^{j} (\mod p)\)

由二项式定理可知,\(C_n^m\)为\((1+x)^n\)展开式中\(x^m\)项前面的系数

\(\because m=q_2p+r_2,\therefore q_2=m/p,r_2=m-\lfloor \frac{m}{p} \rfloor \times p\)

\(C(n,m)x^{m}\equiv C(q_1,q_2)x^{p\times q_2}\times C(r_1,r_2)x^{r_2} (\mod p)\)

即\(C_n^m\equiv C_{q_1}^{q_2}·C_{r_1}^{r_2} (\mod p)\),得证。

推论1 \(C(n,m)\)为奇数的充要条件为,在二进制表示下(\(p=2\)),\(\forall i\in[0,k],a_i\geq b_k\)。

证明:首先,\(C(0,0)=1,C(0,1)=0,C(1,0)=1,C(1,1)=1\)。

考虑\(C(n,m)\mod 2\),则对于\(n\)上的为1的位\(a_i\),\(b_i\)能取0或1,对于\(a_i=0\),则\(b_i\)必须为0,得证。

例题:HDU4349 Xiao Ming's Hope 结论就是\(2^{a_i=1的个数}\)

(2) 扩展Lucas定理

前置知识:卢卡斯定理 中国剩余定理

扩展Lucas定理用于求解以下公式:

\(C_a^b \mod p\),其中\(p\)不一定是质数。

由整数的唯一分解定理,对\(p\)进行质因数分解,\(p=p_1^{a_1}\times p_2^{a_2}\times .. \times p_k^{a_k}\),显然\(\forall i,j \in [1,k], i\neq j, (p_i^{a_i},p_j^{a_j})=1\)。

提高篇中已证明

标签:limits,组合,int,sum,基础,times,数学,equiv,mod
来源: https://www.cnblogs.com/vivaldi370/p/MathBasic_1_Combinatorial.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有