q w q qwq qwq机房最后一个学二项式反演的人
众所周知 二项式反演可以表示成
f n = ∑ i = 0 n ( − 1 ) i × C n i × g i ⟺ g n = ∑ i = 0 n ( − 1 ) i × C n i × f i f_n=\sum_{i=0}^n (-1)^i\times C_n^i\times g_i\Longleftrightarrow g_n=\sum_{i=0}^n(-1)^i\times C_n^i \times f_i fn=i=0∑n(−1)i×Cni×gi⟺gn=i=0∑n(−1)i×Cni×fi
是一个极其对称的式子,常用表达是
f n = ∑ i = 0 n C n i × g i ⟺ g n = ∑ i = 0 n ( − 1 ) n − i × C n i × f i f_n=\sum_{i=0}^n C_n^i\times g_i\Longleftrightarrow g_n=\sum_{i=0}^n(-1)^{n-i}\times C_n^i \times f_i fn=i=0∑nCni×gi⟺gn=i=0∑n(−1)n−i×Cni×fi
网上有很多很好的证明,比如这个博客,感觉容斥的证明比较形象
我就不写证明了(其实是懒得证
这里只写一些套路的做法和题目
如果要求 b l a b l a blabla blabla恰好有 k k k个 b l a b l a blabla blabla的时候,有时候会很难算,而求至多有 k k k个 b l a b l a blabla blabla的时候会很好算,最经典的就是错排问题,错排问题好像有递推,但二项式反演也是可以做的
设 f i f_i fi表示恰好的方案数, g i g_i gi表示至多的方案数,则有
g n = ∑ i = 0 n C n i × f i g_n=\sum_{i=0}^n C_n^i\times f_i gn=i=0∑nCni×fi
根据二项式反演有
f n = ∑ i = 0 n ( − 1 ) n − i × C n i × g i f_n=\sum_{i=0}^n(-1)^{n-i}\times C_n^i\times g_i fn=i=0∑n(−1)n−i×Cni×gi
然后 g i g_i gi一般在很短的时间内就可以方便求出,再用 g g g求 f f f就可以得到答案
例题:
hdu1465不容易系列之一
错排问题
设 f i f_i fi表示恰好有 i i i个错开, g i g_i gi表示至多有 i i i个错开, g i = i ! g_i=i! gi=i!,然后套上面那个公式就好了
代码如下:
#include
#include
#include
#include
#include
#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;
}
同样有时候至少有 k k k个 b l a b l a blabla blabla的要更好求
设 f i f_i fi表示恰好的方案数, g i g_i gi表示至少的方案数,则有
g k = ∑ i = k n C i k × f i g_k=\sum_{i=k}^nC_i^k\times f_i gk=i=k∑nCik×fi
根据二项式反演有
f k = ∑ i = k n ( − 1 ) i − k × C i k × g i f_k=\sum_{i=k}^n(-1)^{i-k}\times C_i^k\times g_i fk=i=k∑n(−1)i−k×Cik×gi
例题:
bzoj3622: 已经没有什么好害怕的了
首先因为要求是 a > b a>b a>b多 k k k个,所以 a > b a>b a>b的应该有 n + k 2 \frac{n+k}{2} 2n+k个(千万别读错题
为了打着舒服设 f i f_i fi表示至少有 i i i个 a > b a>b a>b的方案数,这个不能直接算,所以考虑 d p dp dp,把 a , b a,b a,b从小到大排序,设 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个至少有 j j j个 a > b a>b a>b的方案数,只需要算出来比当前 a [ i ] a[i] a[i]小的 b b b有多少个,再减一下前面用过的,式子就是 f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] × ( c n t [ i ] − j + 1 ) + f [ i − 1 ] [ j ] f[i][j]=f[i-1][j-1]\times (cnt[i]-j+1)+f[i-1][j] f[i][j]=f[i−1][j−1]×(cnt[i]−j+1)+f[i−1][j]
算出来以后再用 f [ n ] [ i ] f[n][i] f[n][i]带入上面的式子里去算最终恰好的答案
注意 f [ n ] [ i ] f[n][i] f[n][i]用的时候还要 × ( n − i ) ! \times (n-i)! ×(n−i)!,因为其他位置是随便放的
代码如下:
#include
#include
#include
#include
#include
#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: 集合计数
首先还是设 f i f_i fi表示恰好交集有 i i i个元素的, g i g_i gi表示至少的,那么 g i = C n i × ( 2 2 n − i − 1 ) g_i=C_n^i\times (2^{2^{n-i}}-1) gi=Cni×(22n−i−1)
这里的含义就是钦定 i i i个必须选,剩下的可以选可以不选的有 2 n − i 2^{n-i} 2n−i个集合,这些集合可选可不选,但不能都不选,就有了上式
再套用上面的公式得出
f k = ∑ i = k n ( − 1 ) i − k × C i k × C n i × ( 2 2 n − i − 1 ) f_k=\sum_{i=k}^n(-1)^{i-k}\times C_i^k\times C_n^i\times (2^{2^{n-i}}-1) fk=i=k∑n(−1)i−k×Cik×Cni×(22n−i−1)
O ( n ) O(n) O(n)计算即可,注意后面那个 2 2 n − i 2^{2^{n-i}} 22n−i貌似不能直接快速幂因为指数不能   m o d   p \bmod p modp,要用 2 2 t = ( 2 2 t − 1 ) 2 2^{2^t}=(2^{2^{t-1}})^2 22t=(22t−1)2倒着枚举算
代码如下:
#include
#include
#include
#include
#include
#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;
}