SelectToy(容斥)

这篇博客探讨了ABC如何从N个包含不同玩具的箱子里选择箱子,确保每种玩具至少出现一次的问题。通过输入N(箱子数量)和M(玩具种类),求解选择方案总数对1,000,000,007取模的结果。文章提出从总方案数减去非法方案数,并利用容斥原理进行计算。在计算过程中,涉及二进制表示的集合、子集的概念,以及使用类似于线段树的分治方法来高效地计算方案数。" 112243827,10540485,Vue源码学习:对象转数组与实用技巧,"['Vue.js', 'JavaScript', '数据处理', '性能优化', '编程技巧']
摘要由CSDN通过智能技术生成

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 的总方案数,集合S没被选到的总方案数。S是个二进制数表示的集合。
Ans=TotalΣ(gSJudge(S))
Judge(S)=(|S|mod2==0?1:1)
gS=ffullsetS
问题来了
怎么快速知道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  ) ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值