组合数
定义
$\binom{n}{k} $ 表示从n个数中无序选出k个数的方案数。
根据定义,有 \(\binom{n}{k}=\frac{n!}{k!(n-k)!}\)
恒等式
上述基本的恒等式一般都是直接拆开组合数后证明。
特别的恒等式
二项式定理:
上指标求和:
平行求和:
交错和:
范德蒙德恒等式:
范德蒙德卷积:
上述略显复杂的恒等式基本都需要基本恒等式去证明,也有着更为重要的应用。
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 定理
- 求极大的或模数不为质数或模数过小的组合数的方法。理论基础为恒等式:
证明在此略去。然后就可以直接暴力做了。
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] 卡农
- 注意到我们有四个性质需要满足:
- 奏响次数为偶数次
- 非空
- 互不相同
- 无序
-
无序很好解决,先视为有序再在最后除以 \(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;
}

浙公网安备 33010602011771号