ZROI#999

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

[sum_{ssubseteq 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
" , ( ans % mod + mod ) % mod ) ;
    system ("pause") ; return 0 ;
}

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

[sum_{Ssubseteq 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
" , ( 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
" , ( ans % mod + mod ) % mod ) ;
    system ("pause") ; return 0 ;
}
May you return with a young heart after years of fighting.
原文地址:https://www.cnblogs.com/Equinox-Flower/p/11486647.html