把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

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

点此看题面

  • 一个\(n\)个元素的集合有\(2^n\)个不同的子集。
  • 要求从这些子集中选出至少一个集合,使得选出集合的交集大小恰好为\(k\),求方案数。
  • \(n\le10^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\times 2^{2^{n-i}}-1 \]

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

\[2^{2^{n-i}}=2^{2^{n-i-1}\times 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\n",(t%X+X)%X),0;
}
posted @ 2021-03-28 11:16  TheLostWeak  阅读(77)  评论(0编辑  收藏  举报