机房最后一个学二项式反演的人
众所周知 二项式反演可以表示成
是一个极其对称的式子,常用表达是
网上有很多很好的证明,比如这个博客,感觉容斥的证明比较形象
我就不写证明了(其实是懒得证
这里只写一些套路的做法和题目
恰好和至多的转换
如果要求 恰好有 个 的时候,有时候会很难算,而求至多有 个 的时候会很好算,最经典的就是错排问题,错排问题好像有递推,但二项式反演也是可以做的
设
表示恰好的方案数,
表示至多的方案数,则有
根据二项式反演有
然后
一般在很短的时间内就可以方便求出,再用
求
就可以得到答案
例题:
hdu1465不容易系列之一
错排问题
设
表示恰好有
个错开,
表示至多有
个错开,
,然后套上面那个公式就好了
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LL long long
#define N 25
using namespace std;
int n;
LL fac[N],f[N],C[N][N];
inline void prework(){
fac[0]=1;
for(int i=1;i<=20;i++) fac[i]=fac[i-1]*i;
C[0][0]=1;
for(int i=1;i<=20;i++) C[i][0]=1;
for(int i=1;i<=20;i++)
for(int j=1;j<=i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];
for(int i=1;i<=20;i++)
for(int j=0;j<=i;j++)
if((i-j)&1) f[i]-=C[i][j]*fac[j];
else f[i]+=C[i][j]*fac[j];
}
int main(){
prework();
while(~scanf("%d",&n)){
printf("%lld\n",f[n]);
}
return 0;
}
恰好和至少的转换
同样有时候至少有 个 的要更好求
设
表示恰好的方案数,
表示至少的方案数,则有
根据二项式反演有
例题:
bzoj3622: 已经没有什么好害怕的了
首先因为要求是
多
个,所以
的应该有
个(千万别读错题
为了打着舒服设 表示至少有 个 的方案数,这个不能直接算,所以考虑 ,把 从小到大排序,设 表示前 个至少有 个 的方案数,只需要算出来比当前 小的 有多少个,再减一下前面用过的,式子就是
算出来以后再用
带入上面的式子里去算最终恰好的答案
注意
用的时候还要
,因为其他位置是随便放的
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LL long long
#define N 2005
using namespace std;
template<class T>inline void rd(T &x){
x=0; short f=1; char c=getchar();
while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
x*=f;
}
const int mod=1e9+9;
int n,k,C[N][N],f[N][N],a[N],b[N],cnt[N],ans,fac[N];
inline void prework(int n){
C[0][0]=1;
for(int i=1;i<=n;i++) C[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1LL*fac[i-1]*i%mod;
}
int main(){
rd(n); rd(k);
if((n+k)&1) return puts("0"),0;
prework(n); k=(n+k)>>1;
for(int i=1;i<=n;i++) rd(a[i]);
for(int i=1;i<=n;i++) rd(b[i]);
sort(a+1,a+n+1); sort(b+1,b+n+1);
int now=0;
for(int i=1;i<=n;i++){
while(b[now+1]<a[i] && now<n) now++;
cnt[i]=now;
}
for(int i=0;i<=n;i++) f[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]=(1LL*f[i-1][j-1]*max((cnt[i]-j+1),0)%mod+1LL*f[i-1][j])%mod;
for(int i=k;i<=n;i++){
f[n][i]=1LL*f[n][i]*fac[n-i]%mod;
if((i-k)&1) (ans+=mod-1LL*C[i][k]*f[n][i]%mod)%=mod;
else (ans+=1LL*C[i][k]*f[n][i]%mod)%=mod;
}
printf("%d\n",ans);
return 0;
}
bzoj2839: 集合计数
首先还是设
表示恰好交集有
个元素的,
表示至少的,那么
这里的含义就是钦定
个必须选,剩下的可以选可以不选的有
个集合,这些集合可选可不选,但不能都不选,就有了上式
再套用上面的公式得出
计算即可,注意后面那个
貌似不能直接快速幂,要用
倒着枚举算
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 1000005
#define LL long long
#define int LL
using namespace std;
int n,k,fac[N],inv[N];
LL ans;
const int mod=1000000007;
inline int qpow(int x,int k){
int ret=1;
while(k){
if(k&1) ret=1LL*ret*x%mod;
x=1LL*x*x%mod; k>>=1;
} return ret;
}
inline void prework(){
fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1LL*fac[i-1]*i%mod;
inv[n]=qpow(fac[n],mod-2); for(int i=n;i;i--) inv[i-1]=1LL*inv[i]*i%mod;
}
inline LL C(int n,int m){return 1LL*fac[n]*inv[m]%mod*inv[n-m]%mod;}
signed main(){
scanf("%lld%lld",&n,&k); prework();
int bin=2;
for(int i=n;i>=k;i--){
if((i-k)&1) (ans+=mod-C(i,k)*C(n,i)%mod*(bin-1)%mod)%=mod;
else (ans+=C(i,k)*C(n,i)%mod*(bin-1)%mod)%=mod;
bin=1LL*bin*bin%mod;
}
printf("%lld\n",(ans+mod)%mod);
return 0;
}