[BZOJ2839]集合計數(容斥原理+組合數學)
阿新 • • 發佈:2019-02-07
題目描述
題解
首先考慮固定k個元素,方案為
還剩下
但是這樣選出來的有可能有不合法的,交集大小可能大於k,所以要減去k+1,加上k+2…
就是個容斥了
設
那麼答案應該為
容斥係數我剛開始是找的規律,但是後來發現,因為第一項即使不考慮交集大小可以大於k的情況,也會多出來很多重複的情況,因為選取的k個元素和選取的集合可能是重複的。那麼後面進行容斥的時候就要利用組合數將重複的去掉
對於 2n−k
這題如果直接暴力的話會非常慢。比較科學的做法是線性推出逆元然後再預處理階乘和逆元的階乘,以及2的整數次冪。這樣速度就比較優秀了
線性推逆元方法
程式碼
#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);
}