ZROI#999

ZROI#999
很有趣的一道题.本来我是想考虑枚举选几个盒子,但我发现这样并没有对问题有任何简化.
然后就考虑容斥嘛...发现,这个容斥比较简单.
假如令\(f(S)\)\(S\)集合中的玩具不能选的方案数.
那么答案就是:

\[\sum_{s\subseteq T}{(-1)^{|S|}f(S)} \]

那么\(f(S)\)是啥呢?就是\(2^{n-|S|}\).
然后直接套入就好.
怎么理解呢?
考虑容斥的基本形式:
答案=总方案数-不满足一个限制的方案数+不满足两个限制的方案数-不满足三个限制的方案数...
这不就是这个式子嘛....于是愉快\(50pts\).
至于\(n\)高达\(10^6\)的时候,则需要使用\(FMT\)(即高维前缀和)来处理.
\(70pts\)思路见代码后.
\(Code:\)

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = a ; i <= b ; ++ i)
#define per(i,a,b) for (int i = a ; i >= b ; -- i)
#define pii pair < int , int >
#define X first
#define Y second
#define rint read<int>
#define int long long
#define pb push_back

using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;

template < class T >
    inline T read () {
        T x = 0 , f = 1 ; char ch = getchar () ;
        while ( ch < '0' || ch > '9' ) {
            if ( ch == '-' ) f = - 1 ;
            ch = getchar () ;
        }
       while ( ch >= '0' && ch <= '9' ) {
            x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
            ch = getchar () ;
       }
   return f * x ;
}

const int N = 1e6 + 100 ;
const int mod = 1e9 + 7 ;

int n , m , tmp[N] , cnt , num , ans ;
bool box[N][25] ;

inline int quick (int a , int p) {
    int res = 1 ;
    while ( p ) {
        if ( p & 1 ) res = ( res * a ) % mod ;
        a = ( a * a ) % mod ; p >>= 1 ;
    }
    return res ;
}

signed main (int argc , char * argv[]) {
    n = rint () ; m = rint () ; int maxsize = 1 << m ; rep ( i , 1 , n )
    { int k = rint () ; rep ( j , 1 , k ) { int x = rint () ; box[i][x] = true ; } }
    for (int i = 0 ; i < maxsize ; ++ i) {
        cnt = 0 ; num = 0 ;
        for (int j = 0 ; j < m ; ++ j)
            if ( i & ( 1 << j ) )
                tmp[++cnt] = j + 1 ;
        rep ( j , 1 , n ) {
            bool f = true ;
            rep ( k , 1 , cnt ) if ( box[j][tmp[k]] ) f = false ;
            if ( f ) ++ num ;
        }
        int d = ( cnt & 1 ) ? - 1 : 1 ;
        ans = ( ans + d * quick ( 2 , num ) % mod ) % mod ;
    }
    printf ("%lld\n" , ( ans % mod + mod ) % mod ) ;
    system ("pause") ; return 0 ;
}

\(70pts\)并不难,
不过我们的容斥会有些许变化.
我们发现题目可以简化为:
给定\(n\)个二进制数,任选一些数字,求有多少种选择方案使得选出来的数字按位or起来是每一位都是\(1\).
于是我们就令\(g(S)\)表示状态为\(S\)的盒子的个数.
\(f(T)\)表示\(\sum_{S\subseteq T}g(S)\).
那么不难想到有这样一个容斥:

\[\sum_{S\subseteq T}{2^{f(S)}(-1)^{m-|S|}} \]

那么我们考虑,这样求的复杂度怎么样.
计算\(g(S)\)是和读入一起的,所以复杂度是\(O(n*m)\)的.
而计算\(f(T)\)呢?我们考虑这到底是个什么玩意儿.
这其实是状态为\(T\)以及\(T\)的子集的盒子总数.
所以我们要枚举子集的子集,这样的复杂度你可能会认为是\(O(4^m)\)
但其实不是,而是\(O(3^m)\)的,这样的复杂度足以通过\(70pts\).
满分思路见代码后.
\(Code:\)

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = a ; i <= b ; ++ i)
#define per(i,a,b) for (int i = a ; i >= b ; -- i)
#define pii pair < int , int >
#define X first
#define Y second
#define rint read<int>
#define int long long
#define pb push_back
#define lowbit(x) ( ( x ) & ( - x ) )

using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;

template < class T >
    inline T read () {
        T x = 0 , f = 1 ; char ch = getchar () ;
        while ( ch < '0' || ch > '9' ) {
            if ( ch == '-' ) f = - 1 ;
            ch = getchar () ;
        }
       while ( ch >= '0' && ch <= '9' ) {
            x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
            ch = getchar () ;
       }
   return f * x ;
}

const int N = 1e7 + 100 ;
const int mod = 1e9 + 7 ;
int n , g[N] , m , ans , f[N] , siz[N] , p[N] ;

inline int quick (int a , int p) {
    int res = 1 ;
    while ( p ) {
        if ( p & 1 ) res = ( res * a ) % mod ;
        a = a * a % mod ; p >>= 1 ;
    }
    return res ;
}

signed main (int argc , char * argv[]) {
    n = rint () ; m = rint () ; int maxsize = 1 << m ;
    rep ( i , 1 , n ) {
        int k = rint () , res = 0 ;
        while ( k -- ) res |= ( 1 << ( rint () - 1 ) ) ;
        ++ g[res] ;
    }
    for (int i = 0 ; i < maxsize ; ++ i) {
        for (int j = i ; j ; j = ( j - 1 ) & i )
            f[i] += g[j] ;
        f[i] += g[0] ;
    }
    p[0] = 1 ; rep ( i , 1 , n ) p[i] = ( p[i-1] << 1 ) % mod ;
    for (int i = 1 ; i < maxsize ; ++ i) siz[i] = siz[i^lowbit(i)] + 1 ;
    for (int i = 0 ; i < maxsize ; ++ i) {
        int d = ( ( ( m - siz[i] ) & 1 ) ? - 1 : 1 ) ;
        ans = ( ( ans + p[f[i]] * d ) % mod + mod ) % mod ;
    }
    printf ("%lld\n" , ( ans % mod + mod ) % mod ) ;
    system ("pause") ; return 0 ;
}

我们发现上面的复杂度瓶颈其实是在求\(f(T)\)的时候我们需要枚举子集的子集.
那么我们就考虑能否优化掉这个复杂度.经过一番思考我们发现,这是个高维前缀和问题.
所以需要使用\(FMT\),但这个题的\(FMT\)没有你想象的那个长得像\(FFT\)的亚子.
这个题目用到的\(FMT\)是一种经典的形式:子集和形式.
这里用到的十分简单,不需要卷积形式..
然后你考虑,对于每一个集合,
我们如果都能算出来它在每一位上分别缺一个\(1\)(即构成一个\(size-1\)的子集)的方案数之和,
那么我们就能从\(size\)\(1\)的子集一直递推到\(size\)\(m\)的子集,于是我们就愉快的解决了这个问题.
\(Code:\)

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = a ; i <= b ; ++ i)
#define per(i,a,b) for (int i = a ; i >= b ; -- i)
#define pii pair < int , int >
#define X first
#define Y second
#define rint read<int>
#define int long long
#define pb push_back
#define lowbit(x) ( ( x ) & ( - x ) )

using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;

template < class T >
    inline T read () {
        T x = 0 , f = 1 ; char ch = getchar () ;
        while ( ch < '0' || ch > '9' ) {
            if ( ch == '-' ) f = - 1 ;
            ch = getchar () ;
        }
       while ( ch >= '0' && ch <= '9' ) {
            x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
            ch = getchar () ;
       }
   return f * x ;
}

const int N = 1e7 + 100 ;
const int mod = 1e9 + 7 ;
int n , g[N] , m , cnt , num , ans , p[N] , siz[N] ;

signed main (int argc , char * argv[]) {
    n = rint () ; m = rint () ; int maxsize = 1 << m ;
    rep ( i , 1 , n ) {
        int k = rint () , res = 0 ;
        while ( k -- ) res |= ( 1 << ( rint () - 1 ) ) ;
        ++ g[res] ;
    }
    for (int i = 0 ; i < m ; ++ i)
        for (int j = 0 ; j < maxsize ; ++ j)
            if ( j & ( 1 << i ) ) g[j] += g[j^(1<<i)] ;
    p[0] = 1 ; rep  ( i , 1 , n ) p[i] = ( p[i-1] << 1 ) % mod ;
    for (int i = 1 ; i < maxsize ; ++ i) siz[i] = siz[i^lowbit(i)] + 1 ;
    for (int i = 0 ; i < maxsize ; ++ i) {
        int d = ( ( ( m - siz[i] ) & 1 ) ? - 1 : 1 ) ;
        ans = ( ( ans + p[g[i]] * d ) % mod + mod ) % mod ;
    }
    printf ("%lld\n" , ( ans % mod + mod ) % mod ) ;
    system ("pause") ; return 0 ;
}
posted @ 2019-09-08 15:42  Phecda  阅读(132)  评论(0编辑  收藏  举报

Contact with me