1. 程式人生 > >[BZOJ2839]集合計數(容斥原理+組合數學)

[BZOJ2839]集合計數(容斥原理+組合數學)

題目描述

傳送門

題解

首先考慮固定k個元素,方案為Ckn
還剩下2nk個集合,可以任選若干個集合C12nk+C22nk+..+C2nk2nk=22nk
但是這樣選出來的有可能有不合法的,交集大小可能大於k,所以要減去k+1,加上k+2…
就是個容斥了
f(k)=Ckn(22nk)
那麼答案應該為f(k)Ckkf(k+1)Ckk+1+f(k+2)Ckk+2...f(n)Ckn
容斥係數我剛開始是找的規律,但是後來發現,因為第一項即使不考慮交集大小可以大於k的情況,也會多出來很多重複的情況,因為選取的k個元素和選取的集合可能是重複的。那麼後面進行容斥的時候就要利用組合數將重複的去掉
對於2

2nk的求法,可以變成22nkmodφ(p)。根據尤拉定理aφ(p)1(modp),(a,p)=1,可以得出anφ(p)an(modp),而這裡的a和p顯然是互質的。
這題如果直接暴力的話會非常慢。比較科學的做法是線性推出逆元然後再預處理階乘和逆元的階乘,以及2的整數次冪。這樣速度就比較優秀了
線性推逆元方法inv(i)=(pp/i)inv(p%i)%p,初始inv(1)=1

程式碼

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath> using namespace std; #define Mod 1000000007 #define N 1000005 #define LL long long int n,k,f; LL mi[N],mul[N],inv[N],ans; void calc(int n) { mul[0]=1; for (int i=1;i<=n;++i) mul[i]=mul[i-1]*(LL)i%Mod; inv[1]=1; for (int i=2;i<=n;++i) inv[i]=inv[Mod%i]*(Mod-Mod/i)%Mod; inv[0
]=1; for (int i=1;i<=n;++i) inv[i]=inv[i]*inv[i-1]%Mod; mi[0]=1; for (int i=1;i<=n;++i) mi[i]=mi[i-1]*(LL)2%(Mod-1); } LL fast_pow(LL a,int p,int mod) { LL ans=1; for (;p;p>>=1,a=a*a%mod) if (p&1) ans=ans*a%mod; return ans; } LL C(int n,int m) { if (m>n) return 0; return mul[n]*inv[m]%Mod*inv[n-m]%Mod; } int main() { scanf("%d%d",&n,&k); calc(n); f=1; for (int i=k;i<=n;++i,f=-f) ans+=C(n,i)*(fast_pow(2,mi[n-i],Mod)-1)%Mod*C(i,k)%Mod*f; ans=(ans%Mod+Mod)%Mod; printf("%lld\n",ans); }