题目
ABC找到N个箱子,箱子里装着一些玩具,一共有M种玩具,编号从1到M,同一种玩具可能出现在多个箱子里。
ABC决定从中选择一些箱子,把这些箱子中的玩具聚集到一起,必须保证每种玩具至少出现一次。
问ABC一共有多少种选择方案。
这题,正难则反,可以用总的方案数(除去不选)减去至少缺一个位的,这个可以用容斥算。
对于至少缺一个我们可以枚举缺哪一位,如果知道哪些箱子是那一位缺一个的,求出sum,然后ans-(2 sum -1),由于多减了缺2个的就用同样的方法加回去,一直容斥下去。
那么关键是我们如何求至少缺i个的箱子数,这个我们将其变成最多缺i个,将方案排列到一个数组上,会发现,每次二分一个点,将数组分开两半,那么左半边是右半边的子集,且一一对应,这样边二分,边将左半边方案一一对应加到右半边,可以nlogn预处理出来,做的时候反着做就行了。
贴代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#define N 2000001
#define MOD 1000000007
#define M 21
using namespace std;
int n,m,ans;
int f[N],help[M];
void init(){
static int x,y,z;
scanf("%d %d",&n,&m);
help[0]=1;
for (int i=1;i<=m;i++)
help[i]=help[i-1]+help[i-1];
for (int i=1;i<=n;i++){
x=0;
scanf("%d",&y);
for (;y--;)
scanf("%d",&z),x|=help[z-1];
f[x]++;
}
}
void dfs(int l,int r){
static int x,y;
if (l==r)return;
dfs(l,(l+r)/2),dfs((l+r)/2+1,r);
x=(l+r)/2;
y=x-l+1;
for (int i=0;i<y;i++)
f[x+i+1]+=f[l+i];
}
void pre(){
dfs(0,help[m]-1);
}
bool did(int x){
static bool p;
p=0;
while (x){
p^=1;
x-=(x&-x);
}
return p;
}
int calc(int x,int y){
static int s;
if (!y)return 1;
s=calc(x,y/2);
s=(long long)s*s%MOD;
return (y&1)?(long long)s*x%MOD:s;
}
void work(){
for (int i=0;i<help[m];i++)
if (!did(i))
ans=((long long)ans+calc(2,f[i^(help[m]-1)])-1+MOD)%MOD;
else
ans=((long long)ans-calc(2,f[i^(help[m]-1)])+1+MOD)%MOD;
}
void write(){
printf("%d",ans);
}
int main(){
init();
pre();
work();
write();
return 0;
}