您的位置:首页 > 其它

SelectToy(容斥)

2015-08-30 10:10 501 查看
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,gSf_S,g_S分别为选出集合SS的总方案数,集合SS没被选到的总方案数。S是个二进制数表示的集合。

Ans=Total−Σ(gS∗Judge(S))Ans=Total-\Sigma( g_S*Judge(S))

Judge(S)=(|S|mod2==0?−1:1)Judge(S)=(|S| mod2==0 ? -1:1 )

gS=ffullset−Sg_S=f_{fullset-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
;

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  ) ;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: