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]
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 ) ; }
相关文章推荐
- C++中vector使用
- FreeMarker四种变量的用法
- vmware虚拟机配置
- 我的openwrt学习笔记(十八):linux便捷开发命令之alias
- JavaScript检测实例属性, 原型属性
- CodeForces 574B-Bear and Three Musketeers
- java写的九九乘法表
- 1096. Consecutive Factors
- 获取单个文件的MD5值
- Android 技巧
- C语言深入
- java数字游戏
- 优先队列的使用
- git合并分支
- String与StringBuffer的区别
- Axis 2 WebService
- 导入导出Android手机文件
- 欢迎使用CSDN-markdown编辑器
- linux 学习笔记之用户管理命令
- 一篇很全面的freemarker教程-初学者必看