题目大意:
题目链接:https://jzoj.net/senior/#main/show/3170
有
n
n
n个箱子装着
m
m
m个玩具(一个玩具可以在多个箱子内),求有多少种选择箱子的方案使得每种玩具至少有一个。
思路:
设
f
[
i
]
f[i]
f[i]表示
∑
S
1
(
S
&
i
=
S
)
\sum^{}_{S}1(S\&i=S)
∑S1(S&i=S),也就是选择其中一些箱子,会得到一个玩具集合
S
S
S(状压后),如果
i
i
i完全包含
S
S
S,
f
[
i
]
f[i]
f[i]就加一。
那么
f
[
M
A
X
N
−
1
−
i
]
f[MAXN-1-i]
f[MAXN−1−i]就是没有一个玩具在集合
i
i
i中的方案数。
显然答案就是
∑
S
−
1
∣
S
∣
×
(
2
f
[
M
A
X
N
−
1
−
i
]
−
1
)
\sum _S -1^{|S|}\times (2^{f[MAXN-1-i]}-1)
S∑−1∣S∣×(2f[MAXN−1−i]−1)
对于求
f
f
f,可以考虑使用分治。
显然
f
[
i
]
&
f
[
i
+
m
i
d
]
=
f
[
i
]
f[i]\&f[i+mid]=f[i]
f[i]&f[i+mid]=f[i],因为若
f
[
i
]
f[i]
f[i]是
a
b
c
d
abcd
abcd,则
f
[
i
+
m
i
d
]
f[i+mid]
f[i+mid]是
1
a
b
c
d
1abcd
1abcd
这样时间复杂度就降到了
O
(
2
m
log
2
m
)
O(2^m\log 2^m)
O(2mlog2m)。
代码:
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=1000010,MOD=1e9+7,M=(1<<20)+10;
int n,m,ans,MAXN,cnt[M],f[M],power[N];
void work(int l,int r)
{
if (l==r)
{
f[l]=cnt[l];
return;
}
int mid=(l+r)/2;
work(l,mid);
work(mid+1,r);
for (int i=l;i<=mid;i++)
f[i-l+mid+1]+=f[i];
}
int main()
{
freopen("data","r",stdin);
scanf("%d%d",&n,&m);
MAXN=(1<<m);
for (int i=1;i<=n;i++)
{
int sum,x,S=0;
scanf("%d",&sum);
while (sum--)
{
scanf("%d",&x);
S|=(1<<x-1);
}
cnt[S]++;
}
work(0,MAXN-1);
power[0]=1;
for (int i=1;i<=n;i++) power[i]=power[i-1]*2%MOD;
cnt[0]=1;
for (int i=0;i<MAXN;i++)
{
if (i&1) cnt[i]=-cnt[i>>1];
else cnt[i]=cnt[i>>1];
ans=(ans+cnt[i]*(power[f[MAXN-1-i]]-1)%MOD)%MOD;
}
printf("%d\n",(ans%MOD+MOD)%MOD);
return 0;
}