jzoj3170-[GDOI2013模拟4]挑选玩具【容斥,状态压缩,分治】

版权声明:原创,未经作者允许禁止转载 https://blog.csdn.net/Mr_wuyongcong/article/details/89282043

正题


题目大意

n n 个箱子放了若干个玩具,要求选择一些箱子使得 m m 种玩具都有,求方案总数。


解题思路

f S f_S 表示选择只有在集合为 S S 的方案数。
然后答案考虑容斥,那么答案就是 S ( 2 ( f ( S ) ) 1 ) ( 1 ) S \sum_S (2^{(f_{(\sim S)})}-1)*(-1)^{|S|}
我们将集合的表示状压起来。
现在考虑如何快速求 f f 数组,首先 f S = c S f_S=c_S ( c S c_S 表示集合是 S S 的箱子个数)。
然后分治,每次分治时 r r 肯定是若干个 1 1
比如当 l = 0 , r = 11111 l=0,r=11111 时左边边都是 0 X X X X 0XXXX 而右边是 1 X X X X 1XXXX 。也就是右边是左边第一位变成一。那么对于每个区间就有

f i = m f i m + l 1 ( i > m ) f_i=\sum_mf_{i-m+l-1}(i> m)

时间复杂度 O ( 2 m   l o g   2 m ) O(2^m\ log\ 2^m)


c o d e code

#include<cstdio>
using namespace std;
const int M=(1<<20)+10,XJQ=1e9+7;
int n,m,f[M],w[M],p[M],ans,v[M],MS;
void apart(int l,int r)
{
	if(l==r){
		f[l]=v[l];
		return; 
	} 
	int m=(l+r)>>1;
	apart(l,m);apart(m+1,r);
	for(int i=l;i<=m;i++)
	  f[i+m-l+1]+=f[i];
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int z=0,k;
		scanf("%d",&k);
		while(k--){
			int x;scanf("%d",&x);
			z|=(1<<x-1);
		}
		v[z]++;
	}
	MS=1<<m;
	for(int i=0;i<MS;i++)
	  w[i]=w[i>>1]^(i&1);
	p[0]=1;
	for(int i=1;i<=n;i++)
	  p[i]=(p[i-1]<<1)%XJQ;
	apart(0,MS-1);
	for(int i=0;i<MS;i++)
	  (ans+=((w[i]?-1:1)*(p[f[MS-i-1]]-1)))%=XJQ;
	printf("%d",(ans+XJQ)%XJQ);
}

猜你喜欢

转载自blog.csdn.net/Mr_wuyongcong/article/details/89282043
今日推荐