组合数

定义

$\binom{n}{k} $ 表示从n个数中无序选出k个数的方案数。
根据定义,有 \(\binom{n}{k}=\frac{n!}{k!(n-k)!}\)

恒等式

\[{\Large \begin{aligned} & \binom{n}{k}=\binom{n}{n-k} \ 对称\\ & \binom{n}{k}=\frac{n}{k}\binom{n-1}{k-1} \ 吸取\\ & \binom{n}{k}=\binom{n-1}{k-1}+\binom{n-1}{k} \ 加法\\ & \binom{n}{k}=(-1)^k\binom{k-n-1}{k} \ 上指标反转\\ & \binom{r}{m}\binom{m}{k}=\binom{r}{k}\binom{r-k}{m-k} \ 三项式版恒等式 \end{aligned} } \]

上述基本的恒等式一般都是直接拆开组合数后证明。

特别的恒等式

二项式定理:

\[{\Large \begin{aligned} & 对于 ( n \in \mathbb{N} ) 时,\\ & (a + b)^n = \sum_{k=0}^{n} \binom{n}{k} a^{n-k} b^k \ 普通二项式定理\\ & x=y=1时,有特殊情况\sum_k\binom{n}{k}=2^n \\ & 对于 ( n \in \mathbb{R} ),( \left|\frac{x}{y}\right| < 1 ) 时,\\ & (x + y)^n = \sum_{k=0}^{+\infty} \binom{n}{k} x^k y^{n-k} \ 广义二项式定理 \end{aligned} } \]

上指标求和:

\[{\Large \begin{aligned} & \sum _{0\le k \le n} \binom{k}{m}=\binom{n+1}{m+1} \end{aligned} } \]

平行求和:

\[{\Large \begin{aligned} & \sum _{k=0}^m \binom{n+k}{k} =\binom{n+m+1}{m} \\ \end{aligned} } \]

交错和:

\[{\Large \begin{aligned} & \sum _{k=0}^m(-1)^k\binom{n}{k} =(-1)^m\binom{n-1}{m},m\in \mathbb{Z} \end{aligned} } \]

范德蒙德恒等式:

\[{\Large \sum_{-q\le k\le l} \binom{l-k}{m} \binom{q+k}{n} = \binom{l+q+1}{m+n+1}} \]

范德蒙德卷积:

\[{\Large \begin{aligned} & \sum _{k} \binom{r}{k}\binom{s}{n-k} =\binom{r+s}{n} \end{aligned} } \]

上述略显复杂的恒等式基本都需要基本恒等式去证明,也有着更为重要的应用。

P4071 [SDOI2016] 排列计数

  • 显然是将所有数分为两部分来做:在原位上的和不在原位上的。

    • 由于原位是固定的因此只需要选出这些数来就好了。具体有\(num_1={n\choose m}\)

    • 然后考虑其他不在原位置上的数。注意并不能简单地直接去枚举排列,因为这样会导致有一些数会被排列到原位置上去导致重复计数。
      因此我们要运用所谓“错排”的方式来表示方案数。

  • \(D_i\) 表示长度为 \(i\) 的序列的错排方案数。我们假设 \(D_1\)\(D_{i-1}\) 都已经处理完毕,现在来考虑 \(D_i\) 怎么求。

  • 一种直观的想法是由于前 \(i-1\) 个数已经错排好,因此第 \(i\) 个数与其中任意一个交换一定是一种新的错排。
    但是这样真的不漏吗?考虑这样一种情况:有一个数在原位置上,其他数都已经错排好,那么与第 \(i\) 个数交换之后一定是与上文本质不同的错排。

  • 显然有有两个数及以上在原位的序列没办法换出本质不同的错排。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+7;
const int p=1e9+7;
int n,m,pro[N],inv[N],d[N];
void solve()
{
	cin>>n>>m;if(m>n) {cout<<"0\n";return ;}if(m==n){cout<<"1\n";return;}
	cout<<(pro[n]*inv[m]%p*inv[n-m]%p)*d[n-m]%p<<'\n';//<<' '<<(pro[n]*inv[m]%p*inv[n-m]%p)<<' '<<d[n-m]
}
int ksm(int x,int k)
{
	int res=1;
	while(k)
	{
		if(k&1) res=res*x%p;
		k>>=1,x=x*x%p;
	}return res;
}
signed main()
{
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	pro[0]=1;for(int i=1;i<=N-2;i++) pro[i]=pro[i-1]*i%p;
	inv[N-2]=ksm(pro[N-2],p-2);for(int i=N-3;i>=0;i--) inv[i]=inv[i+1]*(i+1)%p;
	d[1]=0,d[2]=1;for(int i=3;i<=N-2;i++) d[i]=(i-1)*(d[i-1]+d[i-2])%p; 
	int T;cin>>T;while(T--) solve();return 0;
}

P3807 【模板】卢卡斯定理/Lucas 定理

  • 求极大的或模数不为质数或模数过小的组合数的方法。理论基础为恒等式:

\[{\Large {n\choose m} \equiv {\left \lfloor n/p \right \rfloor \choose \left \lfloor m/p \right \rfloor }+{n\% p\choose m\% p} \pmod{p} } \]

证明在此略去。然后就可以直接暴力做了。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int p,cnt;
int ksm(int x,int k)
{
	int res=1;
	while(k)
	{
		if(k&1) res=res*x%p;
		k>>=1;x=x*x%p;
	}
	return res;
}
int C(int n,int m)
{
	int jie1=1,jie2=1;
	for(int i=n;i>=n-m+1;i--) jie1=jie1*i%p;
	for(int i=1;i<=m;i++) jie2=jie2*i%p;
	jie2=ksm(jie2,p-2);return jie1*jie2%p;
}
int lucas(int n,int m)
{
	if(m==0) return 1;
	return (C(n%p,m%p)*lucas(n/p,m/p))%p;
}
void solve()
{
	int n,m;cin>>n>>m>>p;n=n+m;cnt=0;
	cout<<lucas(n,m)<<'\n';
}
signed main()
{
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int T;cin>>T;while(T--) solve();
}

P3214 [HNOI2011] 卡农

  • 注意到我们有四个性质需要满足:
  1. 奏响次数为偶数次
  2. 非空
  3. 互不相同
  4. 无序
  • 无序很好解决,先视为有序再在最后除以 \(m!\)

  • 解决完无序的问题,如果先只考虑第一条性质,有一个比较直观的想法:
    奏响次数的奇偶性一定可以被最后一个片段调整。假设现在有 \(k\) 个片段,也就是说前 \(k-1\) 个片段随便选,最后一个片段被唯一确定。
    方案数有 \(A^{i-1}_{2^n-1}\)。(\(2^n-1\) 个非空子集里选 \(i-1\) 个,有序)

  • 然后考虑非空。前 \(k-1\) 个片段我们已经保证了非空,而最后一个片段为空的原因是前 \(k-1\) 个片段已经满足性质,最后一个片段就没有调整的必要了。
    \(k\) 个片段合法的方案数为 \(f_k\),则在无视第三个条件的情况下有 \(f_k=A^{i-1}_{2^n-1}-f_{k-1}\)

  • 然后考虑互不相同。如果和前面某一个片段一样,那么除去这个片段的 \(f_{k-2}\) 一定是合法的(奇偶性不变)。
    这个片段的位置有 \(i-1\) 种,片段本身有 \(2^n-1-(i-2)\) 种(\(-(i-2)\) 是因为合法的长度就是 \(i-2\),不能再与其中的片段一样了。)

  • 综上,对于 \(f_k\) 有递推式 \(f_k=A_{2^n-1}^{i-1}-f_{i-1}-(i-1)(2^n-i+1)f_{i-2}\)

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=1e8+7;
int n,m,f[1000005],a[1000005];
int ksm(int x,int k){
	int res=1;
	while(k){
		if(k&1) res=res*x%p;
		x=x*x%p,k>>=1;
	}return res%p;
}
signed main()
{
	cin>>n>>m;
	int res=ksm(2,n)-1,tmp1=res;f[0]=1;f[1]=0;
	a[1]=1;for(int i=2;i<=m;i++) a[i]=a[i-1]*tmp1%p,tmp1=(res-i+1)%p;
	for(int i=2;i<=m;i++) f[i]=(a[i]-f[i-1]+p-(i-1)*(res+2-i)%p*f[i-2]%p+p)%p;
	int tmp=1;for(int i=1;i<=m;i++) tmp=tmp*i%p;
	cout<<(f[m]*ksm(tmp,p-2)+p)%p<<'\n';return 0;
}
posted @ 2025-01-22 21:33  all_for_god  阅读(73)  评论(0)    收藏  举报