【BZOJ2839】集合计数(广义容斥)

点此看题面

  • 一个(n)个元素的集合有(2^n)个不同的子集。
  • 要求从这些子集中选出至少一个集合,使得选出集合的交集大小恰好为(k),求方案数。
  • (nle10^6)

广义容斥

对于这种恰好为(k)的显然想到容斥,去设(f_i)为交集大小大于等于(i)的方案数,根据广义容斥的式子就可以发现:

[ans=sum_{i=k}^n(-1)^{k-i}C_i^kf_i ]

于是只要考虑如何快速计算(f_i),首先有个选出(i)个数的方案数(C_n^i)

那么选出的所有集合都必须包含这(i)个数,剩下(n-i)个数可以任选,因此总共有(2^{n-i})个包含这(i)个数的集合。

然后我们可以在这些集合中选出至少一个,最终得到:

[f_i=C_n^i imes 2^{2^{n-i}}-1 ]

此时已经可以(O(logn))计算每个(f_i)了,但事实上我们可以发现:

[2^{2^{n-i}}=2^{2^{n-i-1} imes 2}=(2^{2^{n-(i+1)}})^2 ]

所以我们只要维护(2^{2^{n-i}})并从大到小枚举(i),便可实现(O(n))了。

代码:(O(n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define X 1000000007
#define C(x,y) (1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X)//组合数
using namespace std;
int n,k,Fac[N+5],IFac[N+5];
I int QP(RI x,RI y,CI p) {RI t=1;W(y) y&1&&(t=1LL*t*x%p),x=1LL*x*x%p,y>>=1;return t;}
int main()
{
	RI i,p;for(scanf("%d%d",&n,&k),Fac[0]=i=1;i<=n;++i) Fac[i]=1LL*Fac[i-1]*i%X;//预处理阶乘
	for(IFac[i=n]=QP(Fac[n],X-2,X);i;--i) IFac[i-1]=1LL*IFac[i]*i%X;//预处理阶乘逆元
	long long t=0;for(p=2,i=n;i>=k;p=1LL*p*p%X,--i) t+=((i-k)&1?X-1LL:1LL)*C(i,k)%X*C(n,i)%X*(p-1)%X;//广义容斥
	return printf("%d
",(t%X+X)%X),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2839.html