ABC找到N个箱子,箱子里装着一些玩具,一共有M种玩具,编号从1到M,同一种玩具可能出现在多个箱子里。
ABC决定从中选择一些箱子,把这些箱子中的玩具聚集到一起,必须保证每种玩具至少出现一次。
问ABC一共有多少种选择方案。
Input
第一行输入两个整数N和M(1<=N<=1,000,000,1<=M<=20)
接下来N行,每行首先输入Ki(0<=ki<=M),接下来输入Ki个1到M之间互不相同的数,表示玩具的编号。
Output
输出一个整数表示选择方案 mod 1 000 000 007的结果。
考虑从总方案数减去非法方案数
设
fS,gS
分别为选出集合
S
的总方案数,集合
Ans=Total−Σ(gS∗Judge(S))
Judge(S)=(|S|mod2==0?−1:1)
gS=ffullset−S
问题来了
怎么快速知道f值
考虑像线段树一样分治。
递归进去不难,合并节点时,考虑当前节点及其左,右儿子。其左儿子所有元素在二进制的某一位全是0,而右儿子在那一位全是1,这说明左儿子p是p+mid的子集,f[p+mid]+=f[p]
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std ;
#define N 1000010
#define M 22
int i , j , k , n , m , full ;
typedef long long ll ;
const ll mo = 1000000007 ;
int z[N] ;
int mapping[1048576] ;
ll f[1048576] ;
void Solve_f( int l , int r ) {
int m = ( l + r ) >> 1 ;
if( l==r ) {
f[l] = mapping[l] ;
return ;
}
Solve_f( l , m ) ;
Solve_f( m+1 , r ) ;
for( i=l ; i<=m ; i++ ) f[ i + ( m-l ) + 1 ] += f[i] ;
}
ll ans = 0 ;
ll power( int n ) {
if( n==0 ) return 1 ;
ll z = power( n / 2 ) ;
z = z * z % mo ;
if( n % 2 ) z = z * 2 % mo ;
return z ;
}
void dfs( int cnt , int dep , int bi ) {
if( dep==m ) {
if( cnt % 2 ) ans = ( ans - ( power( f[ full - bi ] ) - 1 ) % mo ) % mo ;
else ans = ( ans + ( power( f[ full - bi ] ) - 1 ) % mo ) % mo ;
return ;
}
dfs( cnt , dep+1 , bi ) ;
dfs( cnt+1 , dep+1 , bi + ( 1 << dep ) ) ;
}
int main() {
scanf("%d%d",&n,&m ) ;
for( i=1 ; i<=n ; i++ ) {
scanf("%d",&k ) ;
for( j=1 ; j<=k ; j++ ) {
int a ;
scanf("%d",&a ) ;
z[i] += 1 << a-1 ;
}
mapping[ z[i] ] ++ ;
}
full = ( 1 << m ) - 1 ;
Solve_f( 0 , full ) ;
dfs( 0 , 0 , 0 ) ;
printf("%lld\n", ( ans + mo ) % mo ) ;
}